Skip to content

bug(wasm-sdk): raw byteArray equality filters fail in document queries #3540

@thepastaclaw

Description

@thepastaclaw

Summary

The WASM SDK / evo-sdk document-query path appears to encode equality filter values for generic/raw byteArray document properties incorrectly.

A contract with a valid indexed byteArray field can be deployed successfully, documents can be created successfully, and unfiltered reads work, but an equality query on that indexed byteArray field fails at runtime with:

transport error: grpc error: status: 'Internal error', self: "query: storage: protocol: value error: structure error: not an array of bytes"

This looks like a query-value encoding bug in the WASM SDK, not a contract/schema bug.

Thanks to @thephez for the report and clean repro.

Scope note

This report is intentionally narrower than “all byteArray equality queries fail”.

I re-checked Yappr as a possible counterexample. It shows mixed behavior on identifier-like byte-array fields, not a clean disproof of this bug:

  • some Yappr call sites pass identifier-like byte arrays as base58 strings
  • some pass array-like values / Uint8Array
  • some explicitly work around byte-array equality by using 'in' with a single element because == is unreliable there

So the issue should stay open, but the claim should be scoped to generic/raw (non-identifier) byteArray equality filters rather than all byte-array-backed identifiers.

What works

  • Contract registration succeeds
  • Documents can be created successfully
  • Unfiltered document queries succeed
  • Indexed string queries succeed

What fails

Equality queries on an indexed raw/non-identifier byteArray field fail.

Reproduced with an equality filter value passed as a JS byte-array literal (number[]).

Reproduction

Contract schema

{
  "anchor": {
    "type": "object",
    "documentsMutable": false,
    "canBeDeleted": false,
    "properties": {
      "entryHash": {
        "type": "array",
        "byteArray": true,
        "minItems": 32,
        "maxItems": 32,
        "position": 0
      },
      "chainId": {
        "type": "string",
        "minLength": 1,
        "maxLength": 63,
        "position": 1
      }
    },
    "required": ["entryHash", "chainId"],
    "additionalProperties": false,
    "indices": [
      {
        "name": "byChain",
        "properties": [
          { "chainId": "asc" },
          { "$createdAt": "asc" }
        ]
      },
      {
        "name": "byHash",
        "properties": [
          { "entryHash": "asc" }
        ],
        "unique": true
      }
    ]
  }
}

Document example

{
  "chainId": "dashcore-23-1-2-x86-64-linux-gnu-tar",
  "entryHash": [124,77,80,71,162,82,158,25,118,233,87,249,93,92,159,98,139,39,105,62,225,177,31,206,18,169,249,190,33,131,58,118]
}

Query repro

const docs = await sdk.documents.query({
  dataContractId: CONTRACT_ID,
  documentTypeName: 'anchor',
  where: [['entryHash', '==', [
    124,77,80,71,162,82,158,25,118,233,87,249,93,92,159,98,
    139,39,105,62,225,177,31,206,18,169,249,190,33,131,58,118,
  ]]],
  orderBy: [['entryHash', 'asc']],
  limit: 1,
});

Actual result

transport error: grpc error: status: 'Internal error', self: "query: storage: protocol: value error: structure error: not an array of bytes"

Expected result

The query should return the matching document.

Control case

A string-indexed query on the same contract works:

await sdk.documents.query({
  dataContractId: CONTRACT_ID,
  documentTypeName: 'anchor',
  where: [['chainId', '==', 'dashcore-23-1-2-x86-64-linux-gnu-tar']],
  orderBy: [['chainId', 'asc'], ['$createdAt', 'asc']],
  limit: 5,
});

So this does not look like a general index issue. It appears specific to raw byteArray filter-value encoding.

What I verified in the code

I traced the WASM document-query path locally:

  • packages/wasm-sdk/src/queries/document.rs deserializes document where clauses as serde_json::Value
  • parse_where_clause() converts the filter value via the local json_to_platform_value() helper
  • that helper converts a JSON byte array like [124, 77, ...] into a generic Value::Array(...), with each element becoming an integer platform value
  • so by the time the query reaches Drive, the byteArray equality filter is no longer represented as raw bytes, which matches the runtime error above
  • this code path also bypasses the more format-aware JS/platform-value conversion helper in packages/wasm-dpp2/src/serialization/conversions.rs (js_value_to_platform_value()), so document queries are currently using a separate ad hoc JSON → platform-value conversion path

Suspected fix direction

Likely fix in one of these directions:

  • stop using the local JSON-only conversion path for document query values and route where-clause values through the shared JS/platform-value conversion logic, or
  • make the document query value conversion schema-aware so raw byteArray fields are encoded as bytes instead of generic Value::Array

Related context

  • Adjacent but distinct: fix(sdk): inconsistent document query operator #3039 fixed a different document-query value conversion bug in the same file (packages/wasm-sdk/src/queries/document.rs)
  • Yappr is not a clean counterexample to this report: it mostly queries identifier-like byte-array fields, often using base58 string inputs, and some services already work around == by using 'in'
  • I did not fully re-verify the old legacy js-dash-sdk / dapi-client path end-to-end, so this report is specifically about the WASM SDK / evo-sdk query path

Environment

  • Network: testnetTrusted
  • SDK path: sdk.documents.query(...) / client.getDocuments(...)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions