Skip to content

Conversation

@itzlambda
Copy link
Collaborator

@itzlambda itzlambda commented Dec 19, 2025

Summary

  • Adds payment tracking to miner revenue summaries with paid and tx_hash columns
  • New gRPC endpoints: GetUnpaidMinerRevenueSummary and MarkMinerRevenuePaid
  • Python payment script that fetches unpaid summaries and executes TAO/Alpha transfers

Summary by CodeRabbit

  • New Features

    • Payment status tracking and transaction hash added to miner revenue records; unpaid records are efficiently queryable.
    • New APIs to list unpaid revenue summaries and mark summaries as paid.
    • CLI tool to automate miner payouts: price lookup, transfer execution, and marking payments as paid.
  • Documentation

    • Clarified total_revenue units as USD; period queries now use YYYY-MM-DD date strings.

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

- Add paid/tx_hash columns to miner_revenue_summary table
- Add GetUnpaidMinerRevenueSummary gRPC endpoint for querying unpaid records
- Add MarkMinerRevenuePaid gRPC endpoint for recording payments
- Add pay_miners.py script for automated TAO/Alpha payments via Bittensor
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 19, 2025

Walkthrough

Adds payment-tracking to miner revenue: DB migrations add paid and tx_hash with partial indexes and correct total_revenue comment. Storage, domain, and gRPC layers expose retrieval of unpaid summaries and marking summaries paid. Adds a Python CLI to fetch unpaid summaries, compute and execute payments, and mark records paid.

Changes

Cohort / File(s) Change Summary
Database Migrations
crates/basilica-billing/migrations/036_miner_revenue_payment_tracking.sql, crates/basilica-billing/migrations/037_fix_total_revenue_comment.sql
Add paid BOOLEAN NOT NULL DEFAULT FALSE and tx_hash VARCHAR(128) to billing.miner_revenue_summary; add partial indexes for unpaid rows and non-null tx_hash; update total_revenue column comment to USD.
Storage Layer
crates/basilica-billing/src/storage/miner_revenue.rs, crates/basilica-billing/src/storage/mod.rs
Extend MinerRevenueSummary with paid: bool and tx_hash: Option<String>; add UnpaidMinerRevenueSummaryFilter; add repository methods get_unpaid_summaries, count_unpaid_summaries, mark_summary_paid; update SQL mapping and exports.
Domain Layer
crates/basilica-billing/src/domain/miner_revenue.rs
Add trait methods get_unpaid_summaries(filter) -> Result<(Vec<MinerRevenueSummary>, i64)> and mark_as_paid(id, tx_hash) -> Result<bool> with validation, concurrent fetch/count, and logging.
gRPC Service
crates/basilica-billing/src/grpc/billing_service.rs
Add endpoints get_unpaid_miner_revenue_summary and mark_miner_revenue_paid; map new paid and tx_hash fields; validate inputs and translate service results to gRPC responses/errors.
Protocol Definitions
crates/basilica-protocol/proto/billing.proto
Add RPCs GetUnpaidMinerRevenueSummary and MarkMinerRevenuePaid; add request/response messages and pagination; add paid and tx_hash to MinerRevenueSummary; change one request's period_start/period_end to string; update total_revenue comment to USD.
Payment Automation (CLI)
scripts/pay_miners.py
New Click-based CLI with BillingClient (paginated fetch/mark), MinerPaymentProcessor (price fetch, TAO↔Alpha conversion, payment calculation, execution), exception hierarchy, user confirmation flow, and marking summaries paid after transfers.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as pay_miners.py (CLI)
    participant BillingClient
    participant BillingSvc as gRPC BillingService
    participant Storage as DB/Repository
    participant Processor as MinerPaymentProcessor
    participant Wallet as Wallet/Node
    participant Chain as Blockchain

    User->>CLI: run pay_miners
    CLI->>BillingClient: get_all_unpaid_summaries()
    BillingClient->>BillingSvc: GetUnpaidMinerRevenueSummary RPC
    BillingSvc->>Storage: get_unpaid_summaries(filter)
    Storage-->>BillingSvc: summaries
    BillingSvc-->>BillingClient: response
    BillingClient-->>CLI: summaries

    CLI->>Processor: calculate_payments(summaries)
    Processor->>Processor: fetch_tao_price(CoinGecko)
    Processor-->>CLI: PaymentSummary

    CLI->>User: show summary & request approval
    User-->>CLI: approve

    CLI->>Processor: execute_payments(summary)
    loop per payment
        Processor->>Wallet: unlock & prepare
        Processor->>Chain: transfer TAO/Alpha
        Chain-->>Processor: tx_hash
        Processor->>BillingClient: mark_as_paid(id, tx_hash)
        BillingClient->>BillingSvc: MarkMinerRevenuePaid RPC
        BillingSvc->>Storage: mark_summary_paid(id, tx_hash)
        Storage-->>BillingSvc: success/fail
        BillingSvc-->>BillingClient: response
        BillingClient-->>Processor: confirmation
    end
    Processor-->>CLI: results
    CLI-->>User: final report
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Focus review on:
    • SQL update predicate for mark_summary_paid (ensuring idempotency and correct boolean return).
    • Partial-index correctness and migration safety.
    • gRPC input validation (UUID parsing, tx_hash handling) and proto compatibility.
    • Large Python CLI: external network interactions, decimal/precision handling, and error/retry paths.

Possibly related PRs

Suggested reviewers

  • distributedstatemachine
  • epappas

Poem

🐰 I hopped through rows of unpaid gold,

added hashes, flags, and tales retold.
Coins convert and ledgers spring,
miners grin — the payouts sing.
Tx stamped, rabbit hops — all paid! 💸

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 'feat(billing): add miner revenue payment tracking' clearly and directly describes the main change: introducing payment tracking capabilities to miner revenue, which is the central objective across all modified files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch miner-revenue-summary-exploration

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.

@itzlambda itzlambda force-pushed the miner-revenue-summary-exploration branch 2 times, most recently from ef07807 to 63e5b3a Compare December 19, 2025 09:53
@itzlambda itzlambda force-pushed the miner-revenue-summary-exploration branch from 63e5b3a to f9cc019 Compare December 19, 2025 09:57
Alpha payments were incorrectly passing the converted alpha amount to
add_stake(), which expects TAO. This caused overpayment by the alpha/TAO
exchange rate.
Updates documentation and comments to accurately reflect that
total_revenue stores USD values, not TAO. Also updates bittensor
SDK usage to match v10 API changes.
Replace silent warnings with immediate script termination when
mark_as_paid fails after a successful blockchain transaction.
@itzlambda itzlambda marked this pull request as ready for review December 20, 2025 07:31
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: 0

🧹 Nitpick comments (10)
crates/basilica-billing/migrations/036_miner_revenue_payment_tracking.sql (1)

6-8: Consider adding a unique constraint on tx_hash.

A unique constraint on tx_hash would prevent accidentally marking multiple revenue summaries with the same transaction hash, which could indicate a double-payment bug.

However, if a single blockchain transaction can legitimately pay multiple summaries (batch payments), this constraint would be inappropriate. Please verify the intended payment model.

crates/basilica-billing/src/domain/miner_revenue.rs (2)

182-221: Consider extracting shared validation logic.

The period and pagination validation (lines 186-206) is duplicated from get_summaries. A helper method could reduce duplication:

fn validate_pagination(limit: Option<i64>, offset: Option<i64>) -> Result<()> { ... }

This is a minor refactor that can be addressed later.


223-228: Consider validating tx_hash format.

The current validation only checks for empty strings. Consider adding basic format validation for blockchain transaction hashes (e.g., minimum length, valid characters) to catch malformed inputs early.

This could prevent accidental storage of garbage data like whitespace-only strings or truncated hashes.

🔎 Example validation
 async fn mark_as_paid(&self, id: Uuid, tx_hash: &str) -> Result<bool> {
-    if tx_hash.is_empty() {
+    let tx_hash = tx_hash.trim();
+    if tx_hash.is_empty() || tx_hash.len() < 10 {
         return Err(BillingError::InvalidState {
-            message: "Transaction hash cannot be empty".to_string(),
+            message: "Transaction hash is invalid or too short".to_string(),
         });
     }
crates/basilica-protocol/proto/billing.proto (1)

352-357: Consider using google.protobuf.Timestamp for consistency.

The period_start and period_end fields use string dates (YYYY-MM-DD), while GetMinerRevenueSummaryRequest uses google.protobuf.Timestamp for similar fields (lines 297-298). This inconsistency could cause confusion for API consumers.

However, if this matches the existing pattern in RefreshMinerRevenueSummaryRequest, maintaining consistency with that endpoint may be intentional.

crates/basilica-billing/src/grpc/billing_service.rs (1)

1341-1397: Code duplication with get_miner_revenue_summary response mapping.

The MinerRevenueSummary to proto conversion (lines 1342-1383) duplicates lines 1224-1264. Consider extracting a helper method.

🔎 Suggested helper extraction
impl BillingServiceImpl {
    fn domain_summary_to_proto(s: crate::storage::MinerRevenueSummary) -> basilica_protocol::billing::MinerRevenueSummary {
        basilica_protocol::billing::MinerRevenueSummary {
            id: s.id.to_string(),
            node_id: s.node_id,
            validator_id: s.validator_id.unwrap_or_default(),
            miner_uid: s.miner_uid.unwrap_or(0) as u32,
            miner_hotkey: s.miner_hotkey,
            period_start: Some(prost_types::Timestamp {
                seconds: s.period_start.timestamp(),
                nanos: s.period_start.timestamp_subsec_nanos() as i32,
            }),
            period_end: Some(prost_types::Timestamp {
                seconds: s.period_end.timestamp(),
                nanos: s.period_end.timestamp_subsec_nanos() as i32,
            }),
            total_rentals: s.total_rentals as u32,
            completed_rentals: s.completed_rentals as u32,
            failed_rentals: s.failed_rentals as u32,
            total_revenue: Self::format_decimal(s.total_revenue),
            total_hours: Self::format_decimal(s.total_hours),
            avg_hourly_rate: s.avg_hourly_rate.map(Self::format_decimal).unwrap_or_default(),
            avg_rental_duration_hours: s.avg_rental_duration_hours.map(Self::format_decimal).unwrap_or_default(),
            computed_at: Some(prost_types::Timestamp {
                seconds: s.computed_at.timestamp(),
                nanos: s.computed_at.timestamp_subsec_nanos() as i32,
            }),
            computation_version: s.computation_version as u32,
            created_at: Some(prost_types::Timestamp {
                seconds: s.created_at.timestamp(),
                nanos: s.created_at.timestamp_subsec_nanos() as i32,
            }),
            paid: s.paid,
            tx_hash: s.tx_hash.unwrap_or_default(),
        }
    }
}
crates/basilica-billing/src/storage/miner_revenue.rs (1)

508-510: Minor: param_idx not incremented after period_end binding.

The param_idx variable is incremented after period_start (line 505) but not after period_end (line 509). This works correctly because period_end is the last condition, but it's inconsistent. The same pattern exists in count_unpaid_summaries (lines 558-565).

🔎 Suggested consistency fix
         if filter.period_end.is_some() {
             query.push_str(&format!(" AND period_end <= ${}", param_idx));
+            param_idx += 1;
         }
+        let _ = param_idx; // Mark as intentionally unused after this point
scripts/pay_miners.py (4)

106-106: Remove extraneous f prefixes from strings without placeholders.

Several strings use f-string syntax but contain no placeholders.

🔎 Fix locations
- console.print(f"\n[bold]Payment Context:[/bold]")
+ console.print("\n[bold]Payment Context:[/bold]")

- console.print(f"\n[bold yellow]PAYMENT WAS SUCCESSFUL:[/bold yellow]")
+ console.print("\n[bold yellow]PAYMENT WAS SUCCESSFUL:[/bold yellow]")

- console.print(f"\n[bold]DATABASE RECORD NOT UPDATED:[/bold]")
+ console.print("\n[bold]DATABASE RECORD NOT UPDATED:[/bold]")

- console.print(f"\n[bold red]DO NOT RERUN THIS SCRIPT WITHOUT MANUAL INTERVENTION![/bold red]")
+ console.print("\n[bold red]DO NOT RERUN THIS SCRIPT WITHOUT MANUAL INTERVENTION![/bold red]")

- console.print(f"\n[bold]Total Payments:[/bold]")
+ console.print("\n[bold]Total Payments:[/bold]")

- console.print(f"\n[bold]Pricing:[/bold]")
+ console.print("\n[bold]Pricing:[/bold]")

- self.console.print(f"  [green]Marked as paid in billing[/green]")
+ self.console.print("  [green]Marked as paid in billing[/green]")

Also applies to: 247-247, 252-252, 256-256, 674-674, 689-689, 819-819


419-426: Use raise ... from to preserve exception chain.

When re-raising exceptions, use raise ... from original_error or raise ... from None to properly chain exceptions and provide better tracebacks.

🔎 Example fix pattern
             except json.JSONDecodeError as e:
                 raise BillingServiceError(
                     message=f"Invalid JSON response from billing service: {result.stdout[:200]}...",
                     operation="get_unpaid_summaries",
                     original_error=e,
-                )
+                ) from e

Apply this pattern to all similar locations where custom exceptions are raised from caught exceptions.

Also applies to: 500-507, 543-547, 553-557, 725-731, 737-742, 786-791, 821-828


961-963: Early exit with sys.exit(0) bypasses normal return flow.

Using sys.exit(0) inside _run_payment_flow when no summaries are found is acceptable for a CLI script, but it prevents the function from being reused in other contexts (e.g., testing). Consider raising a specific exception or returning early with a flag instead if testability is desired.


636-640: Catching bare Exception masks important errors; consider catching specific exceptions from bittensor operations.

The code calls multiple bittensor library methods (Subtensor(), subnet(), subnet.tao_to_alpha()) that may raise specific exceptions. The suggested exceptions (ValueError, AttributeError, TypeError) handle conversion errors, but the bittensor library defines specific exceptions like ChainError, ChainConnectionError, and ChainTransactionError that could occur during subnet queries. Either catch specific bittensor exceptions explicitly, or document why a broad exception catch is necessary for graceful fallback behavior.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83f16b1 and 4de42ab.

⛔ Files ignored due to path filters (1)
  • crates/basilica-protocol/src/gen/basilica.billing.v1.rs is excluded by !**/gen/**
📒 Files selected for processing (8)
  • crates/basilica-billing/migrations/036_miner_revenue_payment_tracking.sql (1 hunks)
  • crates/basilica-billing/migrations/037_fix_total_revenue_comment.sql (1 hunks)
  • crates/basilica-billing/src/domain/miner_revenue.rs (3 hunks)
  • crates/basilica-billing/src/grpc/billing_service.rs (3 hunks)
  • crates/basilica-billing/src/storage/miner_revenue.rs (7 hunks)
  • crates/basilica-billing/src/storage/mod.rs (1 hunks)
  • crates/basilica-protocol/proto/billing.proto (3 hunks)
  • scripts/pay_miners.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
crates/basilica-billing/src/domain/miner_revenue.rs (1)
crates/basilica-billing/src/storage/miner_revenue.rs (2)
  • get_unpaid_summaries (109-112)
  • get_unpaid_summaries (484-542)
crates/basilica-billing/src/grpc/billing_service.rs (2)
crates/basilica-protocol/src/gen/basilica.billing.v1.rs (6)
  • get_unpaid_miner_revenue_summary (1038-1067)
  • get_unpaid_miner_revenue_summary (1210-1216)
  • new (687-690)
  • new (1244-1246)
  • mark_miner_revenue_paid (1068-1097)
  • mark_miner_revenue_paid (1217-1223)
crates/basilica-billing/src/domain/miner_revenue.rs (1)
  • new (45-47)
scripts/pay_miners.py (1)
crates/basilica-billing/src/domain/miner_revenue.rs (2)
  • mark_as_paid (36-36)
  • mark_as_paid (223-245)
🪛 Ruff (0.14.8)
scripts/pay_miners.py

106-106: f-string without any placeholders

Remove extraneous f prefix

(F541)


247-247: f-string without any placeholders

Remove extraneous f prefix

(F541)


252-252: f-string without any placeholders

Remove extraneous f prefix

(F541)


256-256: f-string without any placeholders

Remove extraneous f prefix

(F541)


374-374: Possible hardcoded password assigned to: "token_type"

(S105)


394-394: subprocess call: check for execution of untrusted input

(S603)


395-405: Starting a process with a partial executable path

(S607)


422-426: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


478-478: subprocess call: check for execution of untrusted input

(S603)


479-489: Starting a process with a partial executable path

(S607)


503-507: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


544-547: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


554-557: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


581-581: Possible hardcoded password assigned to: "token_type"

(S105)


635-635: Consider moving this statement to an else block

(TRY300)


636-636: Do not catch blind exception: Exception

(BLE001)


656-656: Possible hardcoded password assigned to: "token_type"

(S105)


674-674: f-string without any placeholders

Remove extraneous f prefix

(F541)


682-682: Possible hardcoded password assigned to: "token_type"

(S105)


689-689: f-string without any placeholders

Remove extraneous f prefix

(F541)


692-692: Possible hardcoded password assigned to: "token_type"

(S105)


725-725: Do not catch blind exception: Exception

(BLE001)


726-731: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


737-737: Do not catch blind exception: Exception

(BLE001)


738-742: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


769-769: Possible hardcoded password assigned to: "token_type"

(S105)


786-786: Do not catch blind exception: Exception

(BLE001)


787-791: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


818-818: Abstract raise to an inner function

(TRY301)


818-818: Avoid specifying long messages outside the exception class

(TRY003)


819-819: f-string without any placeholders

Remove extraneous f prefix

(F541)


821-821: Do not catch blind exception: Exception

(BLE001)


824-828: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


926-926: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (22)
crates/basilica-billing/migrations/037_fix_total_revenue_comment.sql (1)

1-5: LGTM!

Good documentation fix. The comment now correctly reflects that total_revenue stores USD values, which aligns with the proto definition update.

crates/basilica-billing/src/storage/mod.rs (1)

24-27: LGTM!

The new UnpaidMinerRevenueSummaryFilter type is appropriately re-exported alongside related types.

crates/basilica-billing/migrations/036_miner_revenue_payment_tracking.sql (1)

10-18: LGTM!

Partial indexes are a good choice here. idx_miner_revenue_summary_unpaid efficiently supports unpaid record queries and will shrink as payments are processed. idx_miner_revenue_summary_tx_hash enables transaction hash lookups for audit purposes.

crates/basilica-billing/src/domain/miner_revenue.rs (1)

28-36: LGTM!

The new trait methods follow consistent patterns with the existing get_summaries method. The mark_as_paid signature appropriately returns a boolean to distinguish between success and not-found/already-paid cases.

crates/basilica-protocol/proto/billing.proto (3)

28-30: LGTM!

The new RPCs follow consistent naming conventions and are logically grouped under miner payment operations.


344-348: LGTM!

Field numbers 18-19 appropriately continue the sequence. The payment tracking fields integrate well with the existing message structure.


364-367: LGTM!

The request structure is appropriately minimal. The inline comment clarifies that id is a UUID.

crates/basilica-billing/src/grpc/billing_service.rs (3)

1262-1263: LGTM!

The paid and tx_hash fields are correctly added to the existing get_miner_revenue_summary response, maintaining consistency with the new payment tracking fields.


1281-1339: LGTM - Well-structured unpaid summaries endpoint.

The implementation correctly:

  • Parses optional date filters with proper validation
  • Applies sensible pagination defaults (limit=100)
  • Reuses the existing filter pattern from get_miner_revenue_summary
  • Uses appropriate error handling and logging

1399-1452: LGTM - Robust mark_miner_revenue_paid implementation.

Good practices observed:

  • Validates both id and tx_hash are non-empty
  • Validates UUID format before calling service
  • Logs operations for audit trail
  • Returns success=false with descriptive message when record not found/already paid (idempotent-friendly)
  • Maps errors to response instead of gRPC status (allows clients to handle gracefully)
crates/basilica-billing/src/storage/miner_revenue.rs (6)

40-44: LGTM - Payment tracking fields added correctly.

The paid boolean and optional tx_hash fields provide the necessary payment state tracking. The positioning after audit fields is logical grouping.


72-79: LGTM - Clean filter struct for unpaid summaries.

The filter appropriately limits to period filtering and pagination, avoiding over-engineering.


107-120: LGTM - Well-documented trait extension.

The trait methods are clearly documented, especially mark_summary_paid which explains the return value semantics (true = updated, false = already paid or not found).


483-542: LGTM - Correct unpaid summaries query implementation.

The query properly:

  • Filters by paid = FALSE as the primary condition
  • Uses parameterized queries for period filters
  • Applies consistent ordering for pagination stability
  • Reuses summary_from_row for mapping

589-607: LGTM - Safe conditional update for marking paid.

The WHERE id = $1 AND paid = FALSE condition ensures:

  1. Idempotency - re-running with same ID is safe
  2. No accidental overwrites of already-paid records
  3. Return value indicates whether update occurred

This is a critical safety feature for the payment workflow.


647-656: LGTM - Test updated for new fields.

The test correctly initializes and verifies the new paid and tx_hash fields.

scripts/pay_miners.py (6)

56-126: LGTM - Well-designed error context and hierarchy.

The PaymentContext dataclass captures all necessary information for debugging payment failures. The format_for_console method provides clear, actionable error output.


210-272: Critical error handling for double-payment prevention is excellent.

The MarkAsPaidError class correctly identifies this as the most dangerous error state. The recovery instructions (lines 258-267) provide clear manual remediation steps including the exact grpcurl command needed. This is crucial for operational safety.


811-828: Critical safety: mark_as_paid failure handling is correct.

This is the most important section of the script. The pattern is correct:

  1. Payment succeeds → tx_hash captured
  2. mark_as_paid called with tx_hash
  3. If mark_as_paid fails → MarkAsPaidError raised with full context
  4. Script halts to prevent double-payment on rerun

The explicit check for mark_result == False (line 817-818) is important - it catches the case where the gRPC call succeeds but the record wasn't updated.


984-988: Good safety check: require --force for multiple periods.

This prevents accidental payment of the wrong period when multiple unpaid periods exist. The error message is clear and actionable.


394-408: Subprocess calls use grpcurl from PATH - acceptable for operational scripts.

The static analysis flagged S603 (subprocess execution) and S607 (partial executable path). For this operational script:

  • grpcurl is expected to be installed and in PATH
  • The input data is from the billing service response (not user input)
  • The script is meant for trusted operator use

This is acceptable, but consider documenting the grpcurl dependency in the docstring.

Also applies to: 478-492


768-805: Response structure is correct for Bittensor SDK v10.

ExtrinsicResponse objects returned by subtensor.transfer() and subtensor.add_stake() contain success, message, and extrinsic_receipt attributes, with extrinsic_receipt holding block information and execution details when wait_for_inclusion=True. The code's use of these attributes aligns with the documented SDK v10 API. Verify the exact field name for accessing the extrinsic hash within extrinsic_receipt against your SDK version's implementation or type hints.

Change period_start and period_end from google.protobuf.Timestamp to
string (YYYY-MM-DD format) for consistency with other miner revenue
summary endpoints.
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: 5

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4de42ab and 170335f.

⛔ Files ignored due to path filters (1)
  • crates/basilica-protocol/src/gen/basilica.billing.v1.rs is excluded by !**/gen/**
📒 Files selected for processing (2)
  • crates/basilica-billing/src/grpc/billing_service.rs (4 hunks)
  • crates/basilica-protocol/proto/billing.proto (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
crates/basilica-billing/src/grpc/billing_service.rs (3)
crates/basilica-protocol/src/gen/basilica.billing.v1.rs (6)
  • get_unpaid_miner_revenue_summary (1038-1067)
  • get_unpaid_miner_revenue_summary (1210-1216)
  • new (687-690)
  • new (1244-1246)
  • mark_miner_revenue_paid (1068-1097)
  • mark_miner_revenue_paid (1217-1223)
crates/basilica-billing/src/domain/miner_revenue.rs (1)
  • new (45-47)
crates/basilica-billing/src/storage/miner_revenue.rs (1)
  • new (128-130)
🔇 Additional comments (6)
crates/basilica-billing/src/grpc/billing_service.rs (2)

23-27: LGTM: Import additions are correct.

The new request/response types are properly imported to support the unpaid summary and payment marking endpoints.


1266-1267: LGTM: Payment tracking fields properly mapped.

The paid and tx_hash fields are correctly mapped from the domain model to the protocol message, with appropriate handling of the optional tx_hash field.

crates/basilica-protocol/proto/billing.proto (4)

28-31: LGTM: New RPCs properly defined.

The GetUnpaidMinerRevenueSummary and MarkMinerRevenuePaid RPCs are correctly added with appropriate request/response types and clear documentation.


333-333: LGTM: Important clarification of revenue units.

The comment update clarifies that total_revenue is in USD rather than TAO, which is important information for API consumers.


345-348: LGTM: Payment tracking fields properly added.

The paid and tx_hash fields are correctly added with appropriate types and field numbers. Adding new fields to existing messages is backward-compatible in protobuf.


350-372: LGTM: New message types are well-defined.

The new message types for fetching unpaid summaries and marking records as paid are properly structured with appropriate field types, clear comments, and consistent pagination patterns.

Consolidates duplicate date parsing and summary-to-proto conversion logic
into reusable helpers. Adds validation to ensure period_start <= period_end.
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: 0

♻️ Duplicate comments (1)
crates/basilica-billing/src/grpc/billing_service.rs (1)

1383-1436: Idempotency concern remains unaddressed.

The endpoint returns success: false with message "Record not found or already paid" when mark_as_paid returns Ok(false) (lines 1420-1425). This conflates two distinct scenarios, making idempotent retries problematic. If a client retries marking an already-paid record (possibly with the same tx_hash), it receives a failure, which may trigger additional retries or alerts.

For robust operation, consider:

  • Treating "already paid with the same tx_hash" as success
  • Distinguishing "not found" from "already paid" in error messages
  • Only failing for genuinely conflicting cases (e.g., already paid with different tx_hash)

This concern was previously raised and remains unresolved.

🧹 Nitpick comments (1)
crates/basilica-billing/src/grpc/billing_service.rs (1)

194-217: Consider simplifying the return type.

The function boxes the Status error only to immediately unbox it at each call site (lines 1242, 1248, 1318, 1324 use map_err(|e| *e)). This adds unnecessary heap allocation and dereferencing without any benefit.

🔎 Proposed simplification
-    fn parse_period_date(
-        date_str: &str,
-        field_name: &str,
-        start_of_day: bool,
-    ) -> Result<chrono::DateTime<chrono::Utc>, Box<Status>> {
+    fn parse_period_date(
+        date_str: &str,
+        field_name: &str,
+        start_of_day: bool,
+    ) -> Result<chrono::DateTime<chrono::Utc>, Status> {
         let date = chrono::NaiveDate::parse_from_str(date_str, "%Y-%m-%d").map_err(|_| {
-            Box::new(Status::invalid_argument(format!(
+            Status::invalid_argument(format!(
                 "{} must be in YYYY-MM-DD format",
                 field_name
-            )))
+            ))
         })?;

         let dt = if start_of_day {
             date.and_hms_opt(0, 0, 0).expect("valid time")
         } else {
             date.and_hms_micro_opt(23, 59, 59, 999_999)
                 .expect("valid time")
         };

         Ok(dt.and_utc())
     }

Then update call sites to remove the unboxing:

         if !req.period_start.is_empty() {
             filter.period_start = Some(
-                Self::parse_period_date(&req.period_start, "period_start", true).map_err(|e| *e)?,
+                Self::parse_period_date(&req.period_start, "period_start", true)?,
             );
         }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 170335f and 8d37290.

📒 Files selected for processing (1)
  • crates/basilica-billing/src/grpc/billing_service.rs (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
crates/basilica-billing/src/grpc/billing_service.rs (2)
crates/basilica-billing/src/domain/miner_revenue.rs (1)
  • new (45-47)
crates/basilica-billing/src/storage/miner_revenue.rs (1)
  • new (128-130)
🔇 Additional comments (3)
crates/basilica-billing/src/grpc/billing_service.rs (3)

126-168: LGTM! Helper function successfully eliminates duplication.

The domain_summary_to_proto helper correctly consolidates the domain-to-proto mapping logic that was previously duplicated. The implementation properly handles Option types, decimal formatting, and timestamp conversions. This addresses the refactoring suggestion from previous reviews.


1240-1250: LGTM! Properly uses the extracted helpers.

The endpoint correctly leverages both parse_period_date and domain_summary_to_proto helpers, successfully addressing the code duplication flagged in previous reviews.

Also applies to: 1287-1290


1306-1381: LGTM! Endpoint correctly implements unpaid summaries retrieval.

The implementation properly validates date ranges (lines 1328-1335), applies pagination with sensible defaults, and leverages the shared helper functions. This addresses the validation concern from previous reviews.

@itzlambda itzlambda merged commit 52f15cb into main Dec 20, 2025
14 checks passed
@itzlambda itzlambda deleted the miner-revenue-summary-exploration branch December 20, 2025 09:22
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