Skip to content

Chaincode development

The GalaChain SDK allows you to write Hyperledger Fabric chaincodes in TypeScript in a more convenient way, while adjusting them to the GalaChain platform.

Key features: - Contract classes - Transaction decorators - Transaction context - Authentication and authorization - DTO types - Objects saved on chain - Error handling - State cache - Recommended project structure - Tracing support

All samples in this document come from the GalaChain chaincode template. You can find the template in our source code in chain-cli/chaincode-template directory, or initialize it with the galachain init command.

Contract classes

The GalaChain SDK allows developers to write chaincodes in an object-oriented way. It reuses the concept of contract classes and contract methods from the Hyperledger Fabric Contract API. Typically, a contract class is a TypeScript class that extends the GalaContract class from the @gala-chain/chaincode library. It is recommended to treat each contract class as a controller in the MVC pattern (Model, View, Controller) and minimize the logic within it.

Sample contract class:

import { Evaluate, GalaChainContext, GalaContract, Submit } from "@gala-chain/chaincode";
import { version } from "../../package.json";
import { AppleTreeDto, FetchTreesDto, PagedTreesDto, fetchTrees, plantTree } from "../apples";

export class AppleContract extends GalaContract {
  constructor() {
    super("AppleContract", version);
  }

  @Submit({
    in: AppleTreeDto
  })
  public async PlantTree(ctx: GalaChainContext, dto: AppleTreeDto): Promise<void> {
    await plantTree(ctx, dto);
  }

  @Evaluate({
    in: FetchTreesDto,
    out: PagedTreesDto
  })
  public async FetchTrees(ctx: GalaChainContext, dto: FetchTreesDto): Promise<PagedTreesDto> {
    return await fetchTrees(ctx, dto);
  }
}

GalaContract is a base class for all contract classes. It provides several features: - It ensures that all contract methods have access to the proper transaction context (GalaChainContext, see Transaction decorators). - It adds common methods for a contract: GetContractVersion, GetContractAPI, GetObjectByKey, and GetObjectHistory. - It saves all writes from the GalaChain state cache to the ledger at the end of a successful transaction (see State cache). - It enhances tracing (see Tracing support).

The constructor GalaContract class requires two parameters: name and version. name is a name of the contract, and version is a version of the contract. Typically, you can read the version from the package.json file and version numbers conventionally follow the npm / semver standards.

Each method of the contract class require two parameters: ctx and dto. ctx is a transaction context, an object that extends Hyperledger Fabric Context class. Aside from the standard Fabric context, it provides some additional methods and properties (see Transaction context).

The second parameter, dto, is an object that contains all parameters of the transaction (see DTO types).

Also, all contract methods are decorated with @Submit, @Evaluate, or @GalaTransaction decorators (see Transaction decorators). These decorators are required for various reasons. For instance, they allow you to properly expose the contract methods in GalaChain, deserialize and validate input parameters, normalize the response, handle authorization, etc.

Transaction decorators

Transaction decorators enhance the contract methods with various features: - They allow to properly expose the contract methods API in GalaChain. - They deserialize and validate the DTO before method is called. - They normalize the chaincode method output from any type to GalaChainResponse. - They handle authorization. - They can be used to ensure uniqueness of the transaction in case of duplicate calls. - They can be used to define actions that should be executed before and after the transaction.

GalaChain defines three decorator types: @Submit, @Evaluate, and @GalaTransaction.

  • @Submit decorator is used for contract methods that modify the ledger state.
  • @Evaluate decorator is used for contract methods that only read the ledger state.
  • @GalaTransaction decorator is used for both types of contract methods, but is more verbose. It is recommended to use @Submit and @Evaluate decorators instead.

All decorators support the following parameters: - in - input DTO class that extends GalaChainDto class from @gala-chain/chaincode library (default: ChainCallDTO class). This parameter is used to properly deserialize and validate the input parameters of the transaction, and to properly expose the contract method API in GalaChain. It is highly recommended to provide a custom dto class as a parameter, otherwise the validation won't work at all: There will be issues with deserialization of non-standard input parameters like nested classes, BigDecimal values etc. - out - output type of the chaincode method (default: "null"). It might be a string representing the type ("number", "string", "boolean", "null", "object"), or a custom class, or an object { "arrayOf": X } where X is a string representing the type or a custom class. This parameter is used to properly expose the contract method API in GalaChain. - description - optional description of the contract method that is presented in GalaChain contract method API definition. - allowedOrgs - optional parameter to define which organizations are allowed to call the contract method. It is a string array with organization names. If not provided, all organizations are allowed to call the contract method. - apiMethodName - optional name of the contract method that should be used in the GalaChain REST API. If not provided, the name of the contract method is used. - sequence - optional parameter for advanced use cases. It means that the method call should actually be defined as a sequence of calls. It is useful when a GalaChain REST API call should consist of multiple calls, and each call should be executed in a separate transaction in a separate block. The sequence of calls is handled by GalaChain REST API. - enforceUniqueKey - ensures that DTO contain a uniqueKey property, which is required to prevent duplicate calls (see Prevent attacks or bad data state from duplicate calls). - before - optional parameter defining a function to be executed before the actual transaction (but after the authorization). - after - optional parameter defining a function to be executed after the actual transaction (but before the state cache is saved to the ledger).

Additionally, @GalaTransaction decorator supports type and verifySignature parameters. type can be GalaTransactionType.SUBMIT or GalaTransactionType.EVALUATE and means whether the transaction is a submit or evaluate transaction. verifySignature can be true or false and means whether the transaction should be verified against the signature. It is NOT recommended to use verifySignature as false, because it disables authorization for the transaction.

@Submit decorator is a shortcut for @GalaTransaction({ type: GalaTransactionType.SUBMIT, verifySignature: true }). @Evaluate decorator is a shortcut for @GalaTransaction({ type: GalaTransactionType.EVALUATE, verifySignature: true }).

Transaction context

GalaChainContext is an object that extends Hyperledger Fabric Context class. Asides from standard Fabric context, it provides some additional methods and properties:

  • callingUser - returns standardized user id with prefix and actual name (note calling user is something different, than user in Fabric CA; see Authentication and authorization).
  • callingUserEthAddress - returns eth address that is derived from calling user public key (see Authentication and authorization).
  • txUnixTime - returns unix time of the transaction.
  • span - returns tracing span of the transaction (see Tracing support).

GalaChainContext also changes the behavior of the stub property. In a standard Fabric context, the stub property returns a ChaincodeStub object. In a GalaChain context, the stub property returns a proxy object that wraps ChaincodeStub in a way to support caching (see State cache).

Finally, it adds some customization to the logger property.

Authentication and authorization

A method call requires authorization when it is marked with @Submit or @Evaluate decorator, or with @GalaTransaction with verifySignature: true.

Authorization is handled chaincode-side, on the basis of secp256k1 signature of the transaction. GalaChain recovers the public key from the signature, and derives the corresponding eth address from the public key. Then, GalaChain checks whether the eth address is registered in GalaChain as a user.

If the user is registered, ctx.callingUser and ctx.callingUserEthAddress properties are set in the transaction context. ctx.callingUser is a standardized user id with prefix and actual name. It may be eth|<user-eth-address> or client|<user-alias>, depending on the way the user was registered (RegisterUser and RegisterEthUser respectively).

If the user is not registered, or the signature is missing or invalid, then the transaction is rejected.

Additional notes: * If a method is exposed but (1) does not require authorization (marked with @GalaTransaction with verifySignature: false), and (2) its DTO does not have a signature, then ctx.callingUser contains the Fabric CA username (client|<ca-username>) and executing ctx.callingUserEthAddress throws an exception. * If a method is exposed, does not require authorization, and the DTO has a signature, then the regular authorization flow is performed.

Additional notes about signatures

A JSON payload to be signed is created from a DTO object without signature and trace properties, with its keys sorted alphabetically, and no end of line character(s) at the end. (Further reading as to why the must be the case can be found in the official Hyperledger Fabric documentation). Sample jq command to produce valid data to sign: jq -cSj "." dto-file.json.

Also, all BigNumber data should be provided as strings (not numbers or directly serialized BigNumber objects) with fixed decimal point notation.

The EC secp256k1 signature should be created for keccak256 hash of the data. The recommended format of the signature is a HEX encoded string, including r + s + v values. Signature in this format is supported by ethers library.

Sample signature:

b7244d62671319583ea8f30c8ef3b343cf28e7b7bd56e32b21a5920752dc95b94a9d202b2919581bcf776f0637462cb67170828ddbcc1ea63505f6a211f9ac5b1b

GalaChain also supports DER encoded signatures for authorization, but since the DER signature does not contain v value (the recovery part), you need to additionally provide signerPublicKey parameter to the transaction DTO.

Sample DER signature (first line), and the corresponding signerPublicKey (second line):

3045022100b7244d62671319583ea8f30c8ef3b343cf28e7b7bd56e32b21a5920752dc95b902204a9d202b2919581bcf776f0637462cb67170828ddbcc1ea63505f6a211f9ac5b
04fa7d9e30902207fd821a1518ce777e1935a45e52180d6a6339f37c3e3f759d1a64e33ed1e334070d37731f6ce3f4a5daa6ee4c9884f21860601fed892d40b2a9

Restricting access by organization name

You can restrict access to a contract method by organization name using allowedOrgs parameter of the transaction decorators. It is a string array with organization MSP names.

For example, if you want to allow only CuratorOrg and FarmerOrg to call a contract method, you can use:

GalaChain authorization will check whether the Fabric CA user which called the transaction belongs to one of the allowed organizations. Thus, the check is not related with user profile saved on chain, but related with the CA user which called the transaction.

@Submit({
  in: AppleTreeDto,
  allowedOrgs: ["CuratorOrg", "FarmerOrg"]
})

Additionally, if you don't want to hardcode the organization names in the contract code, you can use AUTHORITY_ORG_NAME const from @gala-chain/chaincode library. It takes the organization name from AUTHORITY_ORG_NAME environment variable (which defaults to CuratorOrg).

DTO types

We consider DTO as an object that contains all parameters of the transaction (transaction input parameters). It is passed as a second parameter to the contract method and deserialized with the use of transaction decorator in parameter.

Each DTO class should extend ChainCallDTO class from @gala-chain/chaincode library. It defines some additional fields that are required for GalaChain to properly handle the transaction: - signature - optional signature of the transaction. It is required for authorization (see Authentication and authorization). - signerPublicKey - optional signer public key of the transaction. It is required for authorization when the transaction is signed with DER signature (see Authentication and authorization). - uniqueKey - optional unique key of the transaction. It is required to prevent duplicate calls (see Prevent duplicate calls). - trace - optional tracing span of the transaction (see Tracing support).

Sample DTO class:

import { ChainCallDTO, StringEnumProperty } from "@gala-chain/api";
import { Type } from "class-transformer";
import { ArrayNotEmpty, ValidateNested } from "class-validator";

export enum Variety {
  GALA = "GALA",
  GOLDEN_DELICIOUS = "GOLDEN_DELICIOUS"
}

export class AppleTreeDto extends ChainCallDTO {
  @StringEnumProperty(Variety)
  public readonly variety: Variety;

  public readonly index: number;
}

export class AppleTreesDto extends ChainCallDTO {
  @ValidateNested({ each: true })
  @Type(() => AppleTreeDto)
  @ArrayNotEmpty()
  public readonly trees: AppleTreeDto[];
}

GalaChain uses class-transformer and class-validator libraries for DTO serialization and validation. It also provides some additional decorators for DTO properties, like @StringEnumProperty, or @BigNumberProperty. You should consult the documentation of these libraries, especially for more complex use cases (including but not limited to): Nested objects, arrays of objects, etc. (note decorators for trees property in the sample above).

Optionally, you can provide a @JSONSchema decorator from the class-validator-jsonschema library, either for whole DTO class, or for each property. It is used to generate a JSON schema for the DTO, which is used in GalaChain REST API.

Objects saved on chain

GalaChain uses the same validation and serialization libraries for objects saved on chain as for DTOs (class-transformer and class-validator). Accordingly, you can use the same decorators for objects saved on chain as for DTOs.

import { BigNumberProperty, ChainKey, ChainObjectBase, StringEnumProperty } from "@gala-chain/api";
import BigNumber from "bignumber.js";
import { IsString } from "class-validator";
import { Variety } from "./types";

export class AppleTree extends ChainObject {
  static INDEX_KEY = "GCAPPL";

  @ChainKey({ position: 0 })
  @IsString()
  public readonly plantedBy: string;

  @ChainKey({ position: 1 })
  @StringEnumProperty(Variety)
  public readonly variety: Variety;

  @ChainKey({ position: 2 })
  public readonly index: number;

  public readonly plantedAt: number;

  @BigNumberProperty()
  public applesPicked: BigNumber;
}

Aside from standard validation and serialization, GalaChain provides a @ChainKey decorator. It is used to define parts of the key of the object saved on chain. For instance in the sample above, the key of the object saved on chain consists of INDEX_KEY, plantedBy, variety, and index properties. Since it is build from multiple properties, it is called a composite key.

Consider you have appleTree which is an instance of AppleTree class, and you want to save it on chain. You can use putChainObject method from @gala-chain/chaincode library:

await putChainObject(ctx, appleTree);

If you want to delete it from chain, you can use deleteChainObject method:

await deleteChainObject(ctx, appleTree);

If you have a key of the object saved on chain (key), you can use getChainObject method to get the object from chain (AppleTree class is required to properly deserialize the object):

await getObjectByKey(ctx, AppleTree, key);

You can get object history with getObjectHistory method:

await getObjectHistory(ctx, key);

And you can check if the object exists on chain with objectExists method:

await objectExists(ctx, key);

Additionally, since GalaChain uses composite keys, you can get all objects with the same prefix using the getObjectByPartialCompositeKey method. For instance, if you want to get all gala apple trees planted by a farmer, you can use:

await getObjectByPartialCompositeKey(ctx, AppleTree.INDEX_KEY, ["farmer1", Variety.GALA], AppleTree);

There is also a relevant method that uses pagination (can be used only read-only transactions): getObjectByPartialCompositeKeyWithPagination.

Ranged objects

GalaChain also supports ranged objects. Ranged objects do not use composite keys, so they can be used in Hyperledger Fabric range queries.

Instead of ChainObject class, you should use RangedChainObject class. Then, you can use @ChainKey decorator to define the key parts of the object saved on chain the same way as for ChainObject class. In order to put ranged object on chain, you should use putRangedChainObject method.

Error handling

We recommend handling errors with exceptions. The GalaChain SDK provides a ChainError class that extends the Node.js Error class, which additionally contains: * code property mapped to a corresponding HTTP code in GalaChain REST API. * key property which is an autogenerated string key from the error class name (for easier debugging). * payload property which is an optional object with additional information about the error.

The GalaChain SDK provides also several predefined error classes which contain proper code values: ValidationError, UnauthorizedError, PaymentRequiredError, ForbiddenError, NotFoundError, ConflictError, NoLongerAvailableError, DefaultError, RuntimeError, NotImplementedError. You may use them in your code or (preferably) create your own error classes that extend one of the predefined error classes.

When a contract method throws an error: * no state changes are saved to the ledger; * the error is logged; * the error is automatically handled, so the response is always a GalaChainResponse object (in case of error the response object contains error properites Status, Message, ErrorCode, ErrorKey, and ErrorPayload); * the transaction is saved on the ledger in transaction history.

State cache

When you get state in Hyperledger Fabric, it always returns the latest value from the ledger. When you update the state in a method, and get it again in the same method, it returns the same value as before the update. To avoid this behavior, the GalaChain SDK has a built-in state cache.

This way, when you get state in a transaction method, and update it in the same method, the second get returns the updated value.

The state cache also prevents inconsistent state in case of exceptions. Since all state changes are flushed to the ledger only at the end of a successful transaction, if an exception is thrown, the state is not updated.

Prevent duplicate calls

Accidental (or maliciously intentional) duplicate calls of some transactions could potentially lead to bad data states, spend of additional token quantities, application layer vulnerabilities, or other ill effects. To prevent this class of problems, DTOs can contain a uniqueKey property. It is an optional string, provided client-side, that is used to prevent duplicate calls. If the same uniqueKey is provided in two different transactions, the second transaction is rejected with UniqueTransactionConflictError error.