Composite Key

The current implementation of SimpleContract can commit and obtain ledger states of the same data type. However, in a real-world scenario, your chaincode implementation will more than likely manage assets of different types. In this subsection, we show how to do this by leveraging composite keys.

ChaincodeStub interface has two functions to work with composite keys:

  • createCompositeKey(objectType: string, attributes: string[]): string — returns a key composed of the objectType string and a list of attributes

  • splitCompositeKey(compositeKey: string): SplitCompositekey — returns the parts of the composite key, reverse operation for createCompositeKey

The objectType and attributes are expected to have only valid UTF8 strings and should not contain U+0000 (nil byte; it is used as a separator in composite keys) or U+10FFFF (the biggest and unallocated code point).

Now, let’s adapt our operations to accept multiple types of assets. To keep it simple, we will just add an argument to operations, specifying which type would be processed.

  1. put(objType, k, v)

  2. get(objType, k)

  3. del(objType, k)

We will also accept a type equal to an empty string to operate with simple key-value pairs like we did before.

🚩You must have noticed that getByRange is not in the list above. It is because the usage of getStateByRange is restricted: you cannot pass the strings containing null characters, which are separators in composite keys, in it. Now, we will leave getByRange as it is and learn how to perform the range queries based on composite keys in the next section.

In terms of implementation, we need to modify the input arguments, which will add new logic that leverages composite keys. To support both composite key and simple key-value pairs operations, let’s define a _createCompositeKey helper function. The function will compose and return a final key string to pass into the ChaincodeStub API.

_createCompositeKey(ctx, objType, key) {
    if (!key || key === "") {
        throw new Error(`A key should be a non-empty string`);
    } 

    if (objType === "") {
        return key;
    } 

    return ctx.stub.createCompositeKey(objType, [key]);
}

Now, we can update the put, get, and del functions.

async put(ctx, objType, key, value) {
   const compositeKey = this._createCompositeKey(ctx, objType, key);
   await ctx.stub.putState(compositeKey, Buffer.from(value));
} 

async get(ctx, objType, key) {
   const compositeKey = this._createCompositeKey(ctx, objType, key);
   const value = await ctx.stub.getState(compositeKey);
   if (!value || value.length === 0) {
       throw new Error(`The asset ${key} of type ${objType} does not exist`);
   } 

   return value.toString();
} 

async del(ctx, objType, key) {
   const compositeKey = this._createCompositeKey(ctx, objType, key);
   await ctx.stub.deleteState(compositeKey);
}

Last updated

Was this helpful?