Skip to content

feat transientdata: typed accessor for Fabric transient data #108

Description

@alejandroGM0

Summary

Add a typed TransientData accessor on HypernateContext that wraps ChaincodeStub.getTransient with type-safe reads, raw byte access, and a single dedicated exception for the only new failure mode key absent from the transient map. Purely additive on top of main, with no changes to Registry, StubMiddleware, or util.JSON.

Motivation

ChaincodeStub.getTransient returns Map<String, byte[]>. The canonical Fabric pattern for sensitive payloads is:

  1. The client puts the payload in the transient map off-ledger.
  2. The chaincode reads it, decodes UTF-8, and JSON-deserializes into a DTO.
  3. The chaincode persists it typically to a private data collection.

Today step 2 is hand-rolled in every contract. Common foot-guns:

  • Forgetting to check whether the key is present.
  • Forgetting that a key can map to a null or zero-length byte[].
  • Manual UTF-8 + JSON wiring duplicated across handlers.
  • Silent fall-through when the JSON is malformed.

With #107 in flight registry.privateData "col".mustCreate dto, step 3 becomes typed end-to-end. A typed accessor for step 2 closes the loop and gives a fully typed transient → private-data flow without any raw ChaincodeStub JSON handling.

Proposal

New package hu.bme.mit.ftsrg.hypernate.transientdata

@Loggable(Loggable.DEBUG)
public final class TransientData {
  private final ChaincodeStub stub;

  public TransientData(final ChaincodeStub stub) {
    this.stub = stub;
  }

  public <T> T mustRead(String name, Class<T> clazz)
      throws TransientKeyNotFoundException, SerializationException;

  public <T> T tryRead(String name, Class<T> clazz)
      throws SerializationException;

  public byte[] mustReadBytes(String name) throws TransientKeyNotFoundException;

  public byte[] tryReadBytes(String name);
}
@StandardException
public class TransientKeyNotFoundException extends HypernateException {}

Exposed via HypernateContext

@Getter private final TransientData transientData;
this.transientData = new TransientData(middlewareChain.getFirst());

Uses middlewareChain.getFirst, the same effective stub that Registry already consumes, so any future read-side middleware applies uniformly.

Behavior

  • A key is treated as absent when any of the following holds:

    • stub.getTransient returns null.

    • The returned map does not contain name.

    • The mapped value is null or zero-length byte[].

      This mirrors Registry's "not found" rule data == null || data.length == 0.

  • mustRead and tryRead decode bytes as UTF-8 then call util.JSON.deserialize json, clazz.

  • tryRead returns null only on an absent key. Malformed JSON is not swallowed; it propagates SerializationException, consistent with Registry.tryRead not swallowing SerializationException.

  • mustReadBytes and tryReadBytes return value.clone so caller mutations cannot leak back into Fabric's transient map.

Design Propousal

  • Package per concept. transientdata/ is a distinct domain (off-ledger inputs), parallel to mappers/ and annotations/.
  • Exceptions live with their concept. TransientKeyNotFoundException ships in transientdata/, mirroring SerializationException living in registry/.
  • Single exception root. Extends HypernateException so catch (HypernateException e) keeps working; a JDK NoSuchElementException would break that invariant.
  • New exception only for genuinely new failures. Absent transient key is new; malformed JSON is already covered by SerializationException.
  • mustX / tryX verb pair preserved.
  • Stub-touching APIs are context-exposed instances. util.JSON is static (operates on extracted bytes); Registry is an instance (touches the stub); TransientData follows the latter.
  • Single JSON path. All bytes ↔ DTO conversion goes through util.JSON, preserving deterministic serialization.

Compatibility

  • Fully additive. No existing public signature changes.
  • No modification to Registry, StubMiddleware, util.JSON, or HypernateContract.
  • One new field on HypernateContext transientData with @Getter.
  • One new package transientdata with two classes.
  • Reuses SerializationException and JSON.deserialize.

Tests

Mockito-based TransientDataTest, parallel to RegistryTest:

  • getTransient returns null → TransientKeyNotFoundException for must, null for try.
  • Key missing from the map → same.
  • Value is null or zero-length byte[] → same.
  • Valid JSON → returns the DTO equal to the expected.
  • Malformed JSON → SerializationException propagates from both mustRead and tryRead.
  • mustReadBytes and tryReadBytes: same absence semantics, plus a test that mutating the returned array does not affect a follow-up read defensive copy.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageThe topic needs further inspection/discussion by maintainers

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions