Skip to main content

Philosophy and architecture

All of hlf-core rests on a handful of principles, and once you understand them the rest reads as a consequence. So before moving on to the individual packages, it is worth working through the idea itself.

Principle 1. A single entry point and the command envelope​

In ordinary chaincode each operation is a separate contract function, and adding a new operation changes its interface. We took a different route: the contract has a single function (transportFabricExecute), and any operation is wrapped in a command envelope. What was actually invoked is determined by the name field in the envelope, not by the function name.

Two consequences follow. First, client code uses a single API — transport.send(...) and transport.sendListen(...) — identical for both the blockchain and ordinary services, because the command model is shared (it comes from @ts-core). Second, reads and writes are separated by the isReadonly flag: readonly commands go through evaluate, without writing to the ledger, while the rest go through submit, with the full endorsement cycle.

Principle 2. Application-level signing​

Fabric authenticates the organization — the identity of the gateway and the MSP — but not the end user. In the enterprise that is not enough: we need to know exactly who initiated an operation and to guard against transaction replay. That is why hlf-core puts an application signature (userId and signature) and a nonce into the envelope, a nonce that strictly increases and is stored in state. This way end-to-end user identification and replay protection appear on top of Fabric's standard model.

Principle 3. An ORM over key-value​

Fabric state is key-value with range queries by prefix. On top of it hlf-core provides an ORM-like layer: entities, parent-child relationships via a pair of forward and reverse keys, pagination, and automatic serialization and validation. Keys are built through StateKey so that a secondary lookup by the left-hand prefix of a composite key stays predictable rather than degrading into a random scan over neighboring records.

Layers​

TOKENScoincoin-chaincodeTRANSPORTtransporttransport-commontransport-chaincodeNETWORK / ORMapichaincodeFOUNDATIONcommonBASE@ts-core/common
hlf-core layers: from the platform-neutral common to the domain pair coin.

The breakdown is simple. Everything platform-neutral — what both the client and the chaincode need — is factored into common. From there it branches into two environments: the client side (api, then transport) and the chaincode environment (chaincode, then transport-chaincode). And transport-common keeps their protocol in sync — the very same code on both sides keeps the envelopes from drifting apart. The coin and coin-chaincode pair repeats the same symmetry at the domain level, on top of the transport.

The path of a typical command​

To bring the principles together into a whole, let us trace a command from the client to the chaincode and back.

Clientcreates the command, takes a nonce and signs itEnveloperequest packaging, clear() of defaultstransportFabricExecuteevaluate (readonly) or submitChaincode: intakesignature, public key and nonce checksHandlerbusiness logic, StateProxy — atomic commitResponsereadonly — immediately; submit — from the block by requestId
Command path: a single entry point, application signature, response from the block.
  1. The client creates a command and calls transport.sendListen(cmd, {userId, signature}). Beforehand, the nonce is fetched with a readonly command, incremented, and signed.
  2. The request envelope is assembled, and default values are stripped from it — so as not to bloat the ledger with superfluous data.
  3. The envelope goes through the contract's single function; based on isReadonly, either evaluate or submit is chosen.
  4. On the chaincode side the envelope is parsed and the signature is verified — algorithm, public key, nonce, cryptographic verification — and the command is dispatched to its handler by the name field.
  5. The handler runs the business logic: it writes and reads state through managers, changes accumulate in StateProxy and are committed atomically, and events are emitted via stub.dispatch.
  6. For a readonly command the response is returned immediately. For a write it ends up in a block — the client parses the block and matches the response to the original command by requestId.

More on signature verification is in the Security section, and on the protocol itself in @hlf-core/transport.