Skip to content

Conversation

@HashEngineering
Copy link
Collaborator

@HashEngineering HashEngineering commented Feb 4, 2025

Updates to support tx metadata changes

  1. System Contract
  2. protobuf

Summary by CodeRabbit

  • New Features

    • Protobuf-based TxMetadata batching, publishing with progress, and new examples/tools for creating, publishing, and displaying TxMetadata.
    • Wallet-utils app/contract and utility to list registered names with balances.
  • Improvements

    • Decryption now detects version and supports CBOR and Protobuf; selectable encryption key index and stronger validation.
    • TxMetadataItem gains JSON/Protobuf conversion and revised fields; queries use updatedAt.
  • Documentation

    • README dependency snippet updated (version bumps).
  • Chores

    • Version bumps, safer error handling, SDK version logging, and expanded tests for TxMetadata.

@HashEngineering HashEngineering self-assigned this Feb 4, 2025
Copy link
Member

@Syn-McJ Syn-McJ left a comment

Choose a reason for hiding this comment

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

Looks good

@HashEngineering
Copy link
Collaborator Author

merge with main

Copy link
Member

@Syn-McJ Syn-McJ left a comment

Choose a reason for hiding this comment

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

Looks good

@HashEngineering HashEngineering changed the title Feat/improve txmetadata feat: improve txmetadata features Jul 6, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 8, 2025

Walkthrough

Version and build bumps; large SWIG/FFI remap and ignore rule overhaul; added wallet-utils protobuf and migrated TxMetadata to versioned CBOR/Protobuf batching with encryption, publish, and decryption; updated platform app IDs and examples; expanded tests; minor Rust SDK error-handling and export visibility changes.

Changes

Cohort / File(s) Summary
Version bumps
README.md, build.gradle
Updated DPP/Dash SDK version references and Gradle ext versions.
SWIG/FFI remap & build
dash-sdk-java/build.gradle, dash-sdk-java/src/main/cpp/clone.h, dash-sdk-java/src/main/swig/generics/result.i, dash-sdk-java/src/main/swig/ignore.i
Interpolated SWIG include flag; extensive clone signature remapping to new platform_mobile/... types; removed debug prints in result.i; large rework of ignore.i to match renamed/reshaped SWIG surface.
Java unit test tweaks
dash-sdk-java/src/test/java/.../DataContractTest.java, dash-sdk-java/src/test/java/.../ValueTest.java
Added presence assertions before unwraps; new fetchDashPayDataContractTest; removed dead I16 PlatformValue test path.
Wallet-utils protobuf & TxMetadata model
dpp/src/main/proto/wallet-utils.proto, dpp/src/main/java/.../wallet/TxMetadata.kt, .../TxMetadataDocument.kt, .../TxMetadataItem.kt
Added TxMetadataItem/TxMetadataBatch proto; moved TxMetadataItem package; removed version field in domain model; added toProtobuf/toJson; TxMetadata supports CBOR/Protobuf buffers and new publish/getBuffer helpers; TxMetadataDocument versioned decrypt parsing.
BlockchainIdentity / TxMetadata flow
dpp/src/main/java/.../dashpay/BlockchainIdentity.kt
New createTxMetadata batching, encryption key selection, document creation; publishTxMetaData supports progress and version; decryptTxMetadata extended for two versions and precondition checks.
Platform/system IDs & app mapping
dpp/src/main/java/.../dapiclient/SystemIds.kt, dpp/src/main/java/.../sdk/platform/Platform.kt, dpp/src/main/java/.../wallet/IdentityVerify.kt
Replaced some hard-coded IDs with zero-hash-based identifiers; added wallet-utils IDs; remapped environment app keys to identity-verify; changed IdentityVerify DOCUMENT constant.
Kotlin tests (wallet / tx metadata)
dpp/src/test/kotlin/.../TxMetaDataTests.kt, dpp/src/test/kotlin/.../TxMetadataWalletTest.kt, dpp/src/test/kotlin/.../dashpay/BlockchainIdentityTest.kt
Added/updated tests for protobuf round-trip, sizes, publish/decrypt flows, large-batch splitting, error and derivation-path cases; new wallet-level integration-like tests.
Examples & tools
examples/src/main/kotlin/.../CreateTxMetadata.kt, .../CreateTxMetadataTest.kt, .../DisplayTxMetadata.kt, .../RegisteredNamesWithBalance.kt, .../DisplayIdentityKeys.kt, .../PlatformExplorer.kt
Updated publish signature usage and TxMetadataItem package; added comprehensive test harness and CLI examples; adjusted input handling; added pagination for contested resources; print enhancements for keys/balances.
Rust SDK adjustments
platform-mobile/src/fetch_document.rs, platform-mobile/src/sdk.rs
Replaced unwrap with error-return when building DocumentQuery; removed ferment export attribute from update function; added SDK version tracing log.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App
  participant BI as BlockchainIdentity
  participant TM as TxMetadata
  participant Doc as Documents API

  App->>BI: publishTxMetaData(items, keyParam?, encKeyIndex, version, progress?)
  BI->>BI: createTxMetadata(items, keyParam?, encKeyIndex, version)
  note right of BI: Build per-document payloads (CBOR or Protobuf)<br/>Select encryption key by index and encrypt
  loop per document
    BI->>Doc: publish(Document for wallet-utils data contract)
    Doc-->>BI: ack Document
    BI-->>App: optional progress update
  end
  BI-->>App: List<Document> (published)
Loading
sequenceDiagram
  autonumber
  participant Caller
  participant TMD as TxMetadataDocument
  participant Dec as Decrypt

  Caller->>TMD: decryptTxMetadata(doc, keyParam?)
  TMD->>Dec: decrypt payload -> decryptedBytes
  alt first byte == VERSION_CBOR
    Note right of TMD: txMetadataVersion = CBOR
    TMD-->>Caller: items = Cbor.decodeList(decryptedBytes[1..])
  else first byte == VERSION_PROTOBUF
    Note right of TMD: txMetadataVersion = PROTOBUF
    TMD-->>Caller: items = TxMetadataBatch.parseFrom(decryptedBytes[1..]).items
  else
    TMD-->>Caller: error (unknown version)
  end
Loading
sequenceDiagram
  autonumber
  participant Caller
  participant FD as fetch_document.rs

  Caller->>FD: DocumentQuery::new(...)
  alt Ok(query)
    FD-->>Caller: proceed with query.with_where(...)
  else Err(e)
    Note right of FD: Return Err(String) instead of panicking
    FD-->>Caller: Err(e.to_string())
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Poem

In my burrow I hop through code and logs,
I batch little carrots into tidy cogs.
CBOR or Proto, I choose with a twitch,
Encrypt every nibble, publish without glitch.
IDs aligned, tests hum, no crash—I stash those logs!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% 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 pull request title "feat: improve txmetadata features" is directly related to the primary changes in this changeset. The raw summary shows that the majority of substantive changes center on enhancing transaction metadata capabilities, including introducing protobuf-based serialization (wallet-utils.proto), adding versioning support to TxMetadataDocument and TxMetadataItem, implementing batching of metadata items into documents, and adding new methods like publish() and getBuffer() for handling multiple serialization formats. While the PR also includes supporting infrastructure changes such as system contract reorganization (SystemIds.kt, Platform.kt, IdentityVerify.kt) and extensive C++/SWIG wrapper updates, these appear to be necessary technical implementation details enabling the txmetadata improvements rather than independent feature additions. The title clearly and specifically communicates the core objective of the change.
✨ 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 feat/improve-txmetadata

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

Caution

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

⚠️ Outside diff range comments (1)
README.md (1)

55-60: Fix README version to match published artifact.

Line 55 advertises dppVersion = "2.0.1", but the build currently ships as 2.0.1-TX-SNAPSHOT (see build.gradle Line 2). Anyone following the README will pull a non-existent artifact and break their build. Please update the snippet (or release a 2.0.1 artifact) so the instructions resolve correctly.

🧹 Nitpick comments (3)
dash-sdk-java/src/test/java/org/dashj/platform/sdk/ValueTest.java (1)

42-47: Remove the commented dead code.

The commented //v2.delete() on line 43 should be removed entirely, as the v2 variable no longer exists in this test.

Apply this diff to clean up the commented code:

     v1.delete();
-    //v2.delete();
     v3.delete();
dpp/src/main/proto/wallet-utils.proto (1)

9-9: Consider fixed-point representation for financial fields.

The use of double for exchangeRate (line 9) and originalPrice (line 17) can lead to precision issues with financial calculations due to floating-point representation limitations.

Consider using one of these alternatives:

  • Option 1 (Recommended): Use string representation with a documented format (e.g., "123.45" for currency amounts), allowing the application layer to parse with appropriate decimal precision.
  • Option 2: Use fixed-point representation with separate integer fields for the whole and fractional parts, or use an integer representing the smallest currency unit (e.g., cents).

Example for Option 1:

-  optional double exchangeRate = 4;
+  optional string exchangeRate = 4;  // decimal string, e.g., "1.2345"
-  optional double originalPrice = 12;
+  optional string originalPrice = 12;  // decimal string, e.g., "99.99"

Also applies to: 17-17

dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetaDataTests.kt (1)

226-229: Remove the empty test stub.

publishToPlatform() contains no assertions or behavior. Keeping an empty test just adds noise and trips detekt’s empty-block rule. Drop the method or flesh it out with real coverage.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b32d9f1 and 04bd06b.

⛔ Files ignored due to path filters (3)
  • dash-sdk-android/src/main/rust/Cargo.lock is excluded by !**/*.lock
  • dash-sdk-bindings/Cargo.lock is excluded by !**/*.lock
  • platform-mobile/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (24)
  • README.md (1 hunks)
  • build.gradle (1 hunks)
  • dash-sdk-java/build.gradle (1 hunks)
  • dash-sdk-java/src/main/cpp/clone.h (2 hunks)
  • dash-sdk-java/src/main/swig/generics/result.i (0 hunks)
  • dash-sdk-java/src/main/swig/ignore.i (5 hunks)
  • dash-sdk-java/src/test/java/org/dashj/platform/sdk/DataContractTest.java (3 hunks)
  • dash-sdk-java/src/test/java/org/dashj/platform/sdk/ValueTest.java (1 hunks)
  • dpp/src/main/java/org/dashj/platform/dapiclient/SystemIds.kt (1 hunks)
  • dpp/src/main/java/org/dashj/platform/dashpay/BlockchainIdentity.kt (6 hunks)
  • dpp/src/main/java/org/dashj/platform/sdk/platform/Platform.kt (1 hunks)
  • dpp/src/main/java/org/dashj/platform/wallet/IdentityVerify.kt (1 hunks)
  • dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt (3 hunks)
  • dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt (3 hunks)
  • dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt (4 hunks)
  • dpp/src/main/proto/wallet-utils.proto (1 hunks)
  • dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetaDataTests.kt (5 hunks)
  • dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetadataWalletTest.kt (1 hunks)
  • examples/src/main/kotlin/dashj/org/platform/CreateTxMetadata.kt (2 hunks)
  • examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt (1 hunks)
  • examples/src/main/kotlin/dashj/org/platform/DisplayTxMetadata.kt (2 hunks)
  • examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt (1 hunks)
  • platform-mobile/src/fetch_document.rs (1 hunks)
  • platform-mobile/src/sdk.rs (2 hunks)
💤 Files with no reviewable changes (1)
  • dash-sdk-java/src/main/swig/generics/result.i
🧰 Additional context used
🧬 Code graph analysis (1)
dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt (2)
dpp/src/main/java/org/dashj/platform/dashpay/Profiles.kt (2)
  • get (166-168)
  • get (170-185)
dpp/src/main/java/org/dashj/platform/sdk/platform/Documents.kt (2)
  • get (151-167)
  • get (169-187)
🪛 Buf (1.57.2)
dpp/src/main/proto/wallet-utils.proto

3-3: Files with package "org.dashj.platform.wallet" must be within a directory "org/dashj/platform/wallet" relative to root but were in directory "dpp/src/main/proto".

(PACKAGE_DIRECTORY_MATCH)

🪛 detekt (1.23.8)
examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt

[warning] 74-74: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetaDataTests.kt

[warning] 227-229: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)

🔇 Additional comments (20)
platform-mobile/src/fetch_document.rs (1)

106-109: LGTM! Improved error handling.

The change from .unwrap() to explicit error matching prevents potential panics and provides better error propagation, which is consistent with the function's error handling pattern.

dash-sdk-java/src/test/java/org/dashj/platform/sdk/ValueTest.java (1)

23-48: LGTM! Redundant test code appropriately removed.

The removal of the v2 test code for I16 is appropriate since I16 is already comprehensively tested in createPlatformValuePrimitiveTest() at line 148. This cleanup reduces code duplication without affecting test coverage.

dash-sdk-java/src/test/java/org/dashj/platform/sdk/DataContractTest.java (2)

20-22: LGTM: Improved null-safety.

The defensive pattern of checking isPresent() before calling get() prevents potential NoSuchElementException if the Optional is empty.


34-36: LGTM: Improved null-safety.

Consistent with the pattern above—checking isPresent() before calling get() prevents potential runtime exceptions.

platform-mobile/src/sdk.rs (2)

225-225: LGTM! Good observability improvement.

Adding the SDK version log is consistent with the existing pattern in create_dash_sdk_with_context (line 173) and improves observability.


62-92: No external FFI bindings reference update_sdk_with_address_list; removing its export attribute is safe.

dpp/src/main/java/org/dashj/platform/dapiclient/SystemIds.kt (1)

10-19: LGTM! Wallet-utils identifiers added consistently.

The addition of wallet-utils identifiers and the refactoring of existing IDs to use Sha256Hash.ZERO_HASH follow a consistent pattern and align with the broader wallet-utils integration across the codebase.

dpp/src/main/java/org/dashj/platform/sdk/platform/Platform.kt (1)

80-91: LGTM! App definitions updated to support wallet-utils and identity-verify.

The addition of the wallet-utils app and the environment-specific identity-verify app definitions are consistent with the system-wide migration to wallet-utils identifiers introduced in SystemIds.kt.

examples/src/main/kotlin/dashj/org/platform/DisplayTxMetadata.kt (2)

29-38: LGTM! Improved argument handling with default identity support.

The refactored argument parsing now supports both interactive phrase input and default identity seeds, which improves the utility's flexibility.


55-55: LGTM! Consistent with updated metadata ordering.

The change from createdAt to updatedAt aligns with the broader shift in TxMetadata.kt where ordering and filtering now use $updatedAt instead of $createdAt.

dpp/src/main/java/org/dashj/platform/wallet/IdentityVerify.kt (1)

30-30: LGTM! Document constant updated to reflect identity-verify app.

The DOCUMENT constant change from "dashwallet.identityVerify" to "identity-verify.identityVerify" is consistent with the Platform.kt updates where the identity-verify app definition replaces dashwallet in environment mappings.

examples/src/main/kotlin/dashj/org/platform/CreateTxMetadata.kt (2)

10-10: LGTM! Import updated to reflect TxMetadataItem package relocation.

The import change from org.dashj.platform.contracts.wallet.TxMetadataItem to org.dashj.platform.wallet.TxMetadataItem is consistent with the broader reorganization of wallet-related types into the wallet package.


47-47: LGTM! Updated to use versioned metadata publishing.

The addition of the keyIndex parameter (1) and version parameter (TxMetadataDocument.VERSION_PROTOBUF) aligns with the new versioned metadata handling introduced in TxMetadataDocument.kt and the updated publishTxMetaData signature.

dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt (4)

69-91: LGTM! New publish method follows established patterns.

The new publish() method correctly retrieves the HIGH security level key, calls the platform SDK with appropriate parameters, and returns a Document. The implementation is consistent with the existing create() method pattern.


113-121: LGTM! Version-aware buffer serialization implemented correctly.

The getBuffer() method properly handles both CBOR and Protobuf serialization formats based on the version parameter, with appropriate error handling for invalid versions.


37-37: LGTM! Document constant updated for wallet-utils migration.

The DOCUMENT constant change from "dashwallet.tx_metadata" to "wallet-utils.txMetadata" is consistent with the broader migration to wallet-utils identifiers and data contracts.


130-133: ```bash
#!/bin/bash

Show get() signature in TxMetadata.kt

rg -n 'fun get' -g 'dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt' -C3

Find direct calls to TxMetadata(...).get(

rg -nP 'TxMetadata([^)]).get(' -g '.kt'

Find instantiations of TxMetadata

rg -nP 'val\s+\w+\s*=\sTxMetadata(' -g '.kt'


</blockquote></details>
<details>
<summary>dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt (1)</summary><blockquote>

`27-39`: **LGTM! Version tracking added with good encapsulation.**

The addition of version constants and the `txMetadataVersion` field with a private setter provides clean version tracking for metadata documents. The field is appropriately initialized to `VERSION_UNKNOWN` and updated during decryption.

</blockquote></details>
<details>
<summary>dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt (2)</summary><blockquote>

`8-8`: **LGTM: Package reorganization and new imports.**

The package move to `org.dashj.platform.wallet` and the addition of protobuf/formatting imports appropriately support the new proto conversion features.




Also applies to: 11-12, 15-15

---

`281-281`: **Note: toString() now uses locale-dependent formatting.**

The toString() method now delegates to `toJson()`, which means numeric fields (exchangeRate, originalPrice) will be displayed with locale-specific currency formatting. This is acceptable for debugging/logging but be aware that the string representation will vary across locales.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines 38 to 39
assertEquals(3, dataContract.getDoc_types().size());
assertTrue("didn't find txMetadata", dataContract.getDoc_types().stream().allMatch(type -> type.equals("txMetadata")));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix the assertion logic.

Line 39 uses allMatch(type -> type.equals("txMetadata")), which checks that every doc_type is named "txMetadata". With 3 doc_types, this assertion would only pass if all three have identical names, which violates the principle that data contract doc types should have distinct names.

Additionally, the error message "didn't find txMetadata" is misleading—it suggests checking for existence, but allMatch verifies that all types are "txMetadata".

Did you intend to:

  • Verify that "txMetadata" exists among the doc_types? Use anyMatch:
    -assertTrue("didn't find txMetadata", dataContract.getDoc_types().stream().allMatch(type -> type.equals("txMetadata")));
    +assertTrue("didn't find txMetadata", dataContract.getDoc_types().stream().anyMatch(type -> type.equals("txMetadata")));
  • Or verify specific doc_type names (e.g., three distinct types)? Assert the exact set:
    Set<String> expectedTypes = Set.of("txMetadata", "typeA", "typeB");
    assertEquals(expectedTypes, new HashSet<>(dataContract.getDoc_types()));

Please clarify the intended assertion and apply the appropriate fix.

🤖 Prompt for AI Agents
In dash-sdk-java/src/test/java/org/dashj/platform/sdk/DataContractTest.java
around lines 38-39, the test uses allMatch which wrongly asserts every doc_type
equals "txMetadata". Replace that with anyMatch to assert existence (and
keep/update the failure message to "didn't find txMetadata"), OR if the intent
is to verify all three specific distinct types, assert the exact set of names by
comparing a Set of expected names to new HashSet<>(dataContract.getDoc_types())
so the test validates the exact distinct types.

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

♻️ Duplicate comments (6)
dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt (1)

77-78: Make the error explicit with expected versions.

The message is better than before but still omits supported values. Include both constants.

-            else -> error("unknown txmetadata version $version")
+            else -> error("Unknown tx metadata version $version. Expected $VERSION_CBOR (CBOR) or $VERSION_PROTOBUF (Protobuf)")
dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt (5)

80-97: Proto defaults vs presence — avoid sentinel checks.

Using != 0/"" to infer presence is brittle (floats, legit zeros, future defaults). Prefer hasXxx() or wrapper/optional fields in the proto.

Example:

constructor(p: WalletUtils.TxMetadataItem) : this(
    p.txId.toByteArray(),
    if (p.hasTimestamp()) p.timestamp else null,
    if (p.hasMemo()) p.memo else null,
    if (p.hasExchangeRate()) p.exchangeRate else null,
    // ...
    if (p.otherDataCount > 0) p.otherDataMap else null
)

If fields aren’t optional, consider switching to wrappers (e.g., google.protobuf.*Value).


103-106: Fix for timestamp map assignment looks good.

map["timestamp"] = it resolves the prior critical bug (using to).


166-214: toJson should emit raw numbers and avoid locale-dependent formatting.

Formatting with DecimalFormat.getCurrencyInstance() produces locale-specific strings; JSON consumers typically expect numbers.

-    fun toJson(): Map<String, Any?> {
-        val map = hashMapOf<String, String?>(
+    fun toJson(): Map<String, Any?> {
+        val map = hashMapOf<String, Any?>(
             "txId" to Sha256Hash.wrap(txId).toString()
         )
-        timestamp?.let { map["timestamp"] = it.toString() }
+        timestamp?.let { map["timestamp"] = it }
         memo?.let { map["memo"] = it }
-        exchangeRate?.let {
-            val format = DecimalFormat.getCurrencyInstance()
-            map["exchangeRate"] = format.format(it)
-        }
+        exchangeRate?.let { map["exchangeRate"] = it }
         currencyCode?.let { map["currencyCode"] = it }
         // ...
-        originalPrice?.let {
-            val format = DecimalFormat.getCurrencyInstance()
-            map["originalPrice"] = format.format(it)
-        }
+        originalPrice?.let { map["originalPrice"] = it }

If display formatting is needed, provide a separate toDisplayMap(Locale) helper.


263-269: hashCode byte order reversal — confirm intent or restore for stability.

Using bytes [3,2,1,0] breaks compatibility with prior [0,1,2,3] ordering (affects HashMap/HashSet behavior).

If unintended, revert:

-        return Ints.fromBytes(
-            txId[3],
-            txId[2],
-            txId[1],
-            txId[0]
-        )
+        return Ints.fromBytes(txId[0], txId[1], txId[2], txId[3])

If intentional, document the breaking change and add migration notes/tests.


272-292: customIconUrl mapping fixed.

Correct field (builder.customIconUrl) is now used; previous copy-paste bug resolved.

🧹 Nitpick comments (10)
examples/src/main/kotlin/dashj/org/platform/PlatformExplorer.kt (1)

556-571: Pagination logic is correct, but consider reducing duplication.

The pagination implementation correctly fetches additional pages until fewer than 100 items are returned. However, the API call and printing logic are duplicated between the initial fetch and the loop body.

Consider refactoring to a do-while loop to eliminate duplication:

-                    var contestedResources = result.unwrap()
-                    var list = contestedResources._0
-                    println("${list.size} contested resources: ")
-                    for (item in list) {
-                        println(item.value.text)
-                    }
-
-                    while (list.size == 100) {
-                        val result2 = dashsdk.platformMobileVotingGetContestedResources(
-                            sdk,
-                            "domain",
-                            Identifier(dpnsContractId),
-                            100,
-                            contestedResources._0.lastOrNull()?._0,
-                            false
-                        )
-                        contestedResources = result2.unwrap()
-                        list = contestedResources._0
+                    var contestedResources = result.unwrap()
+                    var startAfter: Any? = null
+                    do {
+                        val pageResult = dashsdk.platformMobileVotingGetContestedResources(
+                            sdk,
+                            "domain",
+                            Identifier(dpnsContractId),
+                            100,
+                            startAfter,
+                            false
+                        )
+                        contestedResources = pageResult.unwrap()
+                        val list = contestedResources._0
                         println("${list.size} contested resources: ")
                         for (item in list) {
                             println(item.value.text)
                         }
-                    }
+                        startAfter = list.lastOrNull()?._0
+                    } while (list.size == 100)
examples/src/main/kotlin/dashj/org/platform/DisplayIdentityKeys.kt (1)

45-49: Consider adding a security warning comment.

The additional output provides useful debugging information for this example program. However, the code prints sensitive data including private keys (lines 45-46) and addresses (line 49). While this is expected for a utility explicitly designed to display identity keys, consider adding a comment warning that this code should never be used in production environments.

Minor: Fix spacing in line 46.

Add a space after the comma for consistency:

-            println("privateKey: ${DumpedPrivateKey(client.params,firstKey.privKeyBytes, true)}")
+            println("privateKey: ${DumpedPrivateKey(client.params, firstKey.privKeyBytes, true)}")
examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt (2)

169-173: Consider removing commented code.

The commented test block for txMetadataItemFour should either be uncommented if needed or removed to maintain code cleanliness.


193-193: Clarify magic number check.

The condition encryptedMetadata[0] != 0.toByte() uses a magic number without explanation. Consider extracting this into a named constant or adding a comment explaining what the first byte represents (e.g., encryption status flag).

dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt (2)

58-66: Guard against short/invalid plaintext and add defensive parse handling.

Accessing decryptedData[0] will throw if the plaintext is too short. Add a minimal-length check and fail fast with a clear message.

     val decryptedData = cipher.decrypt(EncryptedData(iv, encryptedData), keyParameter)
-    val version = decryptedData.copyOfRange(0, 1)[0].toInt() and 0xFF
+    require(decryptedData.isNotEmpty()) { "Decrypted metadata is empty (no version byte present)" }
+    val version = (decryptedData[0].toInt() and 0xFF)

59-63: Consider adding integrity protection (AEAD/HMAC).

AES-CBC provides confidentiality but not integrity. A wrong key or tampering can yield garbage that passes to decoders. If possible, switch to an AEAD mode (e.g., AES-GCM) or include an HMAC over version+payload and verify before parsing.

Would you like a follow-up PR sketching an authenticated format?

dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt (1)

228-231: Don’t flatten otherData into top-level JSON.

Merging otherData into the root risks key collisions (e.g., “memo”) and diverges from toObject() which nests it.

-        otherData?.let {
-            map.putAll(it)
-        }
+        otherData?.let {
+            map["otherData"] = it
+        }
``` <!-- review_comment_end -->

</blockquote></details>
<details>
<summary>dpp/src/test/kotlin/org/dashj/platform/dashpay/BlockchainIdentityTest.kt (3)</summary><blockquote>

`155-157`: **Avoid magic slack in size check.**

`+ 100` is arbitrary. Either compute exact overhead (IV + padding + version byte) or assert `<= MAX_ENCRYPTED_SIZE` on the plaintext before encryption.


Example:

```kotlin
// Check serialized payload length before encrypt
assertTrue(serialized.size <= TxMetadataDocument.MAX_ENCRYPTED_SIZE, "Payload too large")

Or derive a constant for IV(16) + max padding(16) + version(1).


98-103: Assert detected version on decrypt.

Strengthen the round-trip by asserting the parsed version.

-            val decryptedItems = blockchainIdentity.decryptTxMetadata(txMetadataDocument, null)
+            val decryptedItems = blockchainIdentity.decryptTxMetadata(txMetadataDocument, null)
+            assertEquals(TxMetadataDocument.VERSION_PROTOBUF, txMetadataDocument.txMetadataVersion)

60-108: Consider adding a CBOR round-trip test.

Add a mirror of this test using TxMetadataDocument.VERSION_CBOR to validate both paths.

I can draft the CBOR variant if helpful.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 04bd06b and 5ee49e9.

⛔ Files ignored due to path filters (3)
  • dash-sdk-android/src/main/rust/Cargo.lock is excluded by !**/*.lock
  • dash-sdk-bindings/Cargo.lock is excluded by !**/*.lock
  • platform-mobile/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • dash-sdk-java/src/test/java/org/dashj/platform/sdk/DataContractTest.java (2 hunks)
  • dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt (3 hunks)
  • dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt (6 hunks)
  • dpp/src/test/kotlin/org/dashj/platform/dashpay/BlockchainIdentityTest.kt (2 hunks)
  • examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt (1 hunks)
  • examples/src/main/kotlin/dashj/org/platform/DisplayIdentityKeys.kt (2 hunks)
  • examples/src/main/kotlin/dashj/org/platform/PlatformExplorer.kt (1 hunks)
  • examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • dash-sdk-java/src/test/java/org/dashj/platform/sdk/DataContractTest.java
  • examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt
🔇 Additional comments (3)
examples/src/main/kotlin/dashj/org/platform/PlatformExplorer.kt (1)

549-550: LGTM! Necessary mutability for pagination.

The change from val to var is correct and required for reassignment within the pagination loop.

examples/src/main/kotlin/dashj/org/platform/DisplayIdentityKeys.kt (1)

9-10: LGTM!

The new imports are used appropriately in the enhanced output statements.

examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt (1)

142-144: Off-by-one issue resolved.

The previous off-by-one error has been correctly fixed by replacing IntRange(0, count) with repeat(count), ensuring exactly count items are added.

Comment on lines 23 to +26
// 2^16 + 2
val childNumber = ChildNumber(2 shl 15 + 1, true)
const val MAX_ENCRYPTED_SIZE = 4096 - 32 // leave room for a partially filled block and the IV

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Operator precedence bug in child number expression.

2 shl 15 + 1 is parsed as 2 shl (15 + 1) yielding 131072, not “2^16 + 2” as the comment states. Make the intent explicit.

Apply:

-        // 2^16 + 2
-        val childNumber = ChildNumber(2 shl 15 + 1, true)
+        // 2^16 + 2 = 65538
+        val childNumber = ChildNumber((1 shl 16) + 2, true)

Also fix the same expression in tests (BlockchainIdentityTest.kt Lines 172–173).

🤖 Prompt for AI Agents
In dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt around
lines 23–26, the expression `2 shl 15 + 1` has wrong operator precedence and
does not match the comment "2^16 + 2"; replace it with an explicit calculation
such as `(1 shl 16) + 2` (or a literal 65538) to make the intent unambiguous,
and apply the same fix to the identical expression in tests at
BlockchainIdentityTest.kt lines 172–173.

Comment on lines +64 to +71
val version = decryptedData.copyOfRange(0, 1)[0].toInt() and 0xFF
return when (version) {
VERSION_CBOR -> {
val list = Cbor.decodeList(decryptedData)
this.txMetadataVersion = VERSION_CBOR
// use .map to convert to List<TxMetadataItem>
list.map { TxMetadataItem(it as Map<String, Any?>) }
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

CBOR branch ignores version byte; parsing starts at offset 0.

You read a leading version byte (Line 64) but CBOR decoding uses the full buffer instead of payload = decryptedData[1..]. This will corrupt parsing.

-            VERSION_CBOR -> {
-                val list = Cbor.decodeList(decryptedData)
+            VERSION_CBOR -> {
+                val list = Cbor.decodeList(decryptedData.copyOfRange(1, decryptedData.size))
                 this.txMetadataVersion = VERSION_CBOR
                 // use .map to convert to List<TxMetadataItem>
                 list.map { TxMetadataItem(it as Map<String, Any?>) }
             }

Additionally, ensure the serializer prepends the version byte (see verification script below).

🤖 Prompt for AI Agents
In dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt around
lines 64 to 71, the CBOR branch currently decodes the entire decryptedData
including the leading version byte, corrupting the CBOR parse; change the decode
call to use the payload excluding the first byte (copyOfRange(1,
decryptedData.size)) so parsing starts at offset 1, set txMetadataVersion as
before, and return the mapped List<TxMetadataItem>; also update/verify the
serializer to prepend the single-byte version header to the serialized payload
so round-trip encoding/decoding includes the version byte.

Comment on lines +64 to +76
val version = decryptedData.copyOfRange(0, 1)[0].toInt() and 0xFF
return when (version) {
VERSION_CBOR -> {
val list = Cbor.decodeList(decryptedData)
this.txMetadataVersion = VERSION_CBOR
// use .map to convert to List<TxMetadataItem>
list.map { TxMetadataItem(it as Map<String, Any?>) }
}
VERSION_PROTOBUF -> {
val batch = TxMetadataBatch.parser().parseFrom(decryptedData, 1, decryptedData.size - 1)
txMetadataVersion = VERSION_PROTOBUF
batch.itemsList.map { TxMetadataItem(it) }
}
Copy link

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

Verify serializer prepends the version byte.

Decrypt expects a leading version byte (offset 0). Please confirm TxMetadata.getBuffer() returns byteArrayOf(version) + payload.

Run:


🏁 Script executed:

#!/bin/bash
# Verify that serializer prefixes a version byte
set -euo pipefail

echo "Searching for TxMetadata.getBuffer() and version usage..."
fd -a --hidden --fixed-strings "TxMetadata.kt" || true
rg -nP --type=kt -C3 'class\s+TxMetadata\b|fun\s+getBuffer\s*\(|VERSION_CBOR|VERSION_PROTOBUF|byteArrayOf\(\s*.*toByte\(\)\s*\)\s*\+' dpp/ || true

Length of output: 328


🏁 Script executed:

#!/bin/bash
# Search for TxMetadata.kt and inspect getBuffer() implementation
set -euo pipefail

# Use correct type or no type filter
echo "=== Searching for getBuffer() method ==="
rg -nP -C5 'fun\s+getBuffer\s*\(' dpp/src/main/java/org/dashj/platform/wallet/ || true

echo ""
echo "=== Searching for VERSION constants ==="
rg -nP 'VERSION_CBOR|VERSION_PROTOBUF' dpp/src/main/java/org/dashj/platform/wallet/ || true

echo ""
echo "=== Inspecting TxMetadata.kt directly ==="
cat dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt || true

Length of output: 384


🏁 Script executed:

#!/bin/bash
# Dynamically find TxMetadata.kt and inspect it
set -euo pipefail

echo "=== Finding TxMetadata.kt files ==="
TX_METADATA_FILE=$(fd -type f "TxMetadata\.kt" | head -1)
echo "Found: $TX_METADATA_FILE"

echo ""
echo "=== Inspecting TxMetadata.kt ==="
cat "$TX_METADATA_FILE" || true

echo ""
echo "=== Searching for getBuffer() and VERSION constants in same directory ==="
DIR=$(dirname "$TX_METADATA_FILE")
rg -nP 'fun\s+getBuffer|VERSION_CBOR|VERSION_PROTOBUF|byteArrayOf' "$DIR" || true

Length of output: 206


🏁 Script executed:

#!/bin/bash
# Find and inspect TxMetadata.kt with corrected syntax
set -euo pipefail

echo "=== Finding TxMetadata.kt files ==="
TX_METADATA_FILE=$(fd "TxMetadata\.kt$" | head -1)
echo "Found: $TX_METADATA_FILE"

if [ -z "$TX_METADATA_FILE" ]; then
  echo "File not found, searching more broadly..."
  TX_METADATA_FILE=$(find . -name "TxMetadata.kt" 2>/dev/null | head -1)
  echo "Found: $TX_METADATA_FILE"
fi

echo ""
echo "=== Inspecting TxMetadata.kt ==="
cat "$TX_METADATA_FILE"

Length of output: 5124


🏁 Script executed:

#!/bin/bash
# Find where getBuffer() is called and where version byte is added
set -euo pipefail

echo "=== Searching for getBuffer() calls ==="
rg -nP 'getBuffer\s*\(' --context 5 || true

echo ""
echo "=== Searching for encryption/serialization patterns ==="
rg -nP 'encrypt|byteArrayOf.*version|toByte\(\)\s*\+|VERSION_CBOR.*\+|VERSION_PROTOBUF.*\+' --context 3 || true

echo ""
echo "=== Looking at TxMetadataDocument.kt for encryption context ==="
fd "TxMetadataDocument\.kt$" -x cat {} || true

Length of output: 82012


Version byte serialization verified but CBOR decode path has a critical bug.

The version byte IS prepended to encrypted data, but not by TxMetadata.getBuffer(). Instead, it's prepended by the caller in BlockchainIdentity.createTxMetadata() (line 2326: allEncryptedData[0] = version.toByte()).

After decryption in TxMetadataDocument.decrypt(), the decrypted payload contains the version byte at offset 0. However, there's an asymmetry:

  • PROTOBUF path (line 74): Correctly skips version byte with parseFrom(decryptedData, 1, decryptedData.size - 1)
  • CBOR path (line 69): BUG — passes full decryptedData to Cbor.decodeList() without skipping the version byte at offset 0

This will cause CBOR decoding to fail or produce corrupted data. Line 69 should be:

val list = Cbor.decodeList(decryptedData.copyOfRange(1, decryptedData.size))
🤖 Prompt for AI Agents
In dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt around
lines 64 to 76, the CBOR decode path is incorrectly passing the entire
decryptedData (including the prepended version byte) to Cbor.decodeList; update
the CBOR branch to skip the first byte (same as the protobuf branch) by decoding
from decryptedData offset 1 to decryptedData.size so the version byte is not
included in the CBOR payload, then set txMetadataVersion and map the decoded
list to TxMetadataItem as before.

Comment on lines +168 to +175
val parentEncryptionKeyPath = org.bitcoinj.wallet.DerivationPathFactory.get(blockchainIdentity.params).blockchainIdentityECDSADerivationPath()
val parentEncryptionKey = authenticationGroupExtension.identityKeyChain.getKeyByPath(parentEncryptionKeyPath)
.deriveChildKey(ChildNumber(2, true))
val expectedEncryptionKey = parentEncryptionKey
.deriveChildKey(ChildNumber(2 shl 15 + 1, true))
.derive(1)
assertEquals(expectedEncryptionKey, actualEncryptionKey)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use the constant and fix precedence in derivation path.

Avoid duplicating the expression and fix the precedence bug.

-                val expectedEncryptionKey = parentEncryptionKey
-                    .deriveChildKey(ChildNumber(2 shl 15 + 1, true))
-                    .derive(1)
+                val expectedEncryptionKey = parentEncryptionKey
+                    .deriveChildKey(TxMetadataDocument.childNumber)
+                    .derive(1)
🤖 Prompt for AI Agents
In dpp/src/test/kotlin/org/dashj/platform/dashpay/BlockchainIdentityTest.kt
around lines 168–175, you duplicated the derivation expression and used
incorrect operator precedence in ChildNumber(2 shl 15 + 1, true); fix by
introducing a named constant for the derived index (e.g. val
ENCRYPTION_DERIVATION_INDEX = (2 shl 15) + 1) and use that constant in the
deriveChildKey call, ensuring the shift is parenthesized as shown so the shift
happens before the addition.

Comment on lines +36 to +37
val seed = "caution kitchen month seven humble olympic author session dutch direct decrease moment"
// val seed = "half sample spirit edit dawn humor eight refuse hundred suit critic print"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid hardcoding seed phrases, even in test code.

Hardcoded mnemonic seeds pose a security risk if they control real funds or are accidentally reused in production. Consider loading test seeds from environment variables or a secure configuration file.

🤖 Prompt for AI Agents
In examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt around
lines 36-37, the test currently hardcodes a mnemonic seed; replace this by
reading the seed from an environment variable or secure test config (e.g.
System.getenv("TEST_MNEMONIC")) and fail the test with a clear error if the env
var is required but missing, or alternatively generate a random ephemeral
mnemonic at runtime for pure unit tests; ensure any fallback is explicitly
marked "test-only" and add a brief comment/documentation instructing CI/devs to
provide a test-only mnemonic via env var rather than committing real wallets.

println("Tx Metadata: -----------------------------------")
for (doc in documents) {
val txDoc = TxMetadataDocument(doc)
if (txDoc.encryptedMetadata[0] != 0.toByte()) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add bounds check before accessing array index.

Accessing txDoc.encryptedMetadata[0] without verifying the array is non-empty will throw IndexOutOfBoundsException if encryptedMetadata is empty.

Apply this diff to add a bounds check:

-            if (txDoc.encryptedMetadata[0] != 0.toByte()) {
+            if (txDoc.encryptedMetadata.isNotEmpty() && txDoc.encryptedMetadata[0] != 0.toByte()) {
                 println(JSONObject(doc.toJSON()).toString(2))
                 val txList = blockchainIdentity.decryptTxMetadata(txDoc, null)
                 println("  $txList")
🤖 Prompt for AI Agents
In examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt around
line 193, the code accesses txDoc.encryptedMetadata[0] without ensuring the
array is non-empty; add a bounds check by evaluating that
txDoc.encryptedMetadata is not empty (e.g., txDoc.encryptedMetadata.isNotEmpty()
or size > 0) before accessing index 0, and adjust the conditional to only
inspect [0] when the check passes (handle the empty case accordingly).

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