Hyperledger Fabric smart contract data model: protobuf to chaincode state mapping

Hyperledger Fabric smart contract data model: protobuf to chaincode state mappingHow specifying world state data model with protocol buffers can help in developing smart contractsViktor NosovBlockedUnblockFollowFollowingFeb 20Chaincode is a domain specific program which relates to specific business process.

It programmatically accesses two distinct pieces of the ledger — a blockchain, which immutably records the history of all transactions, and a world state that holds a cache of the current value of these states.

The job of a smart contract developer is to take an existing business process that might govern financial prices or delivery conditions, and express it as a smart contract in a programming language.

Protobuf (short for Protocol buffers) are language-neutral, platform-neutral, extensible mechanism for serializing structured data.

Using protocol buffers can help to define data model once and then easily write and read structured data to and from a variety of data sources.

Chaincode stateThe ledger’s current state data represents the latest values for all keys ever included in the chain transaction log.

Since current state represents all latest key values known to the channel, it is sometimes referred to as World State.

Chaincode invocations execute transactions against the current state data.

To make these chaincode interactions extremely efficient, the latest values of all keys are stored in a state database.

Smart contracts primarily put, get and delete states in the world state, and can also query the state change history.

Chaincode “shim” APIs implements ChaincodeStubInterface which contain methods for access and modify the ledger, and to make invocations between chaincodes.

Main methods are:* GetState(key string) ([]byte, error) performs a query to retrieve information about the current state of a object* PutState(key string, value []byte) error creates a new object or modifies an existing one in the ledger world state* DelState(key string) error removes an object from the current state of the ledger, but not its history* GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error)queries the state in the ledger based on given partial composite key* GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error)returns a history of key values across time.

All this methods use string key as record identifier and slice of bytes as state value.

Most of examples use serialized to bytes JSON documents as chaincode state value.

Hyperledger Fabric supports both LevelDB as CouchDB to serve as state database, holding the latest state of each object.

LevelDB is the default key-value state database embedded in every peer.

CouchDB is an optional alternative external state database with more features — it supports rich queries against JSON documents in chaincode state, whereas LevelDB only supports queries against keys.

Querying and updating state with ChaincodeStubInterface methodsAs shown in many examples, assets can be represented as complex structures — Golang structs , which are required to be marshalled to JSON string before putting into chaincode state and unmarshalled after receiving from state.

With ChaincodeStubInterface methods these operations can looks like this:In the example above smart contract code explicitly performs many auxiliary actions:Creating composite keyUnmarshalling data after receiving it from stateMarshalling data before placing it to stateChaincode state operations with CCKitState methods wrapperCCKit, library for creating and testing Hyperledger Fabric golang chaincode, contains wrapper on ChaincodeStubInteface methods to working with chaincode state.

This methods simplifies chaincode key creation and data transformation during work with chaincode state.

Converting from/to bytes while operating with chaincode stateState wrapper allows to automatically marshal golang type to/from slice of bytes.

This type can be:Any type implementing ToByter and FromByter interfaceGolang struct or one of supported types ( int, string, []string)Protocol buffers messageGolang structs automatically marshals/ unmarshals using json.

Marshal andand json.

Umarshal methods.

proto.

Marshal and proto.

Unmarshal are used to convert protobuf.

Creating state keysIn the chaincode data model we often need to store many instances of one type on the ledger, such as multiple commercial papers, letters of credit, and so on.

In this case, the unique key of those instances will be typically constructed from a combination of attributes, for example:`CommercialPaper` + {Issuer} + {PaperId}yielding series of chaincode state entries keys[ `CommercialPaperIssuer1Id1`, `CommercialPaperIssuer2Id2`, …]The logic of creation primary key of an instance can be customized in the code, constructing a composite key of an instance based on a combination of several attributes.

Composite keys can then be used as a normal string key torecord and retrieve values using the PutState() and GetState() functions.

The following snippet shows a list of functions that create and work with composite keys in ChaincodeStubInterface:When putting or getting data to/from chaincode state you must provide key.

CCKit have 3 options for dealing with entries key:Key can be passed explicit to Put methodc.

State().

Put ( `my-key`, &myStructInstance)Key type can implement Keyer interfaceKey can be of Key type — is essentially slice of string, this slice will be automatically converted to string using shim.

CreateCompositeKey method.

and in chaincode you need to provide only type instance, key will be created automatically:c.

State().

Put (&myStructInstance)Entry type can have associate mappingMapping defines rules for namespace (prefix for key), primary and other uniq and non uniq keys for an entity.

Mapping mainly used with protobuf state schema.

Range queriesAs well as retrieving assets with a unique key, ChaincodeStubInterface offers functions the opportunity to retrieve sets of assets based on a range criteria.

 Moreover, composite keys can be constructed to enable queries against multiple components of the key.

The range functions return an iterator StateQueryIteratorInterface over a set of keys matching the query criteria.

The returned keys are in lexical order.

 Additionally, when a composite key has multiple attributes, the range query function, GetStateByPartialCompositeKey(), can be used to search for keys matching a subset of the attributes.

For example, the key of a CommercialPaper composed of Issuer and PaperId attributes, entries can be searched only from one Issuer.

Protobuf state model exampleCCKit protocol buffers example use Commercial paper scenario andimplements same functionality as Node.

JS chaincode sample from official documentation.

Protobuf is a way of encoding structured data in an efficient and extensible format.

Protobuf schema advantages:Schema abstraction layerEncoding the semantics of your business objects once, in .

proto format, is enough to help ensure that the message doesn’t get lost between applications, and that the boundaries you create enforce your business rules.

2.

Extensions — validators etcProtobuf v3 does not support validating required parameters, but there are third party projects for proto validation, for examplehttps://github.

com/mwitkow/go-proto-validators.

It allows to encode, at the schema level, the shape of your data structure, and the validation rules.

3.

Easy Language InteroperabilityBecause Protocol Buffers are implemented in a variety of languages, they make interoperability between polyglot applications in your architecture that much simpler.

If you’re introducing a new service using Java Fabric SDK or Node.

Js Fabric SDK you simply have to hand the .

proto file to the code generator written in the target language and you have guarantees about the safety and interoperability between those architectures.

Defining modelWith protocol buffers, you write a .

proto description of the data structure you wish to store.

From that, the protocol buffer compiler creates a golang struct that implements automatic encoding and parsing of the protocol buffer data with an efficient binary format (or json).

The generated class provides getters and setters for the fields that make up a protocol buffer and takes care of the details of reading and writing the protocol buffer as a unit.

In Commercial Paper example first we define messages, that will be stored in chaincode state or used as events:CommercialPaper will be stored in chaincode stateCommercialPaperId defines unique id part of commercial paper messageIssueCommercialPaper payload for issue transaction and event triggered when new commercial paper issuedBuyCommercialPaper payload for buy transaction and event triggered when commercial paper change ownerRedeemCommercialPaper payload for redeem transaction and event triggered when commercial paper redeemedDefining protobuf to chaincode state mappingProtocol buffers message to chaincode mapper can be used to store schema instances in chaincode state.

Every schema type (protobuf or struct) can have mapping rules:Primary key creation logicNamespace logicSecondary key creation logicChaincodeIn chaincode we simply use generated from .

proto files structures, chaincode state creation predefined in mappings.

Chaincode implementation use CCKit routing and middleware features to structure the code.

TestsWe can test all chaincode use case scenarios using MockStub:.

. More details

Leave a Reply