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
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.
- The client creates a command and calls
transport.sendListen(cmd, {userId, signature}). Beforehand, the nonce is fetched with a readonly command, incremented, and signed. - The request envelope is assembled, and default values are stripped from it — so as not to bloat the ledger with superfluous data.
- The envelope goes through the contract's single function; based on
isReadonly, eitherevaluateorsubmitis chosen. - 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
namefield. - The handler runs the business logic: it writes and reads state through managers, changes
accumulate in
StateProxyand are committed atomically, and events are emitted viastub.dispatch. - 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.