Welcome to the breaking changes documentation for GalaChain SDK major releases. This document outlines the significant changes that may affect users upgrading to a new major version of the library. These changes are considered breaking as they may require modifications to existing codebases to maintain compatibility.
By following this documentation, users can effectively manage the upgrade process and ensure smooth transitions to newer versions of the GalaChain SDK. If you have any questions or need further assistance, please refer to the GalaChain SDK documentation or reach out to the community for support.
The version contains various breaking changes in terms of how authorization is handled, how transactions are signed, and how the API is structured.
However, it is backward compatible with the previous version in terms of the current chaincode state.
The changes are mostly related to the way the SDK is used and how the chaincode is structured.
If you have a current production chain: You don't need to update the data, but you may need to update the code to be compatible with the new version.
The only exception is the authorization, which requires a migration of the roles from allowedOrgs
to allowedRoles
, and setting up the roles for some user profiles.
createAndSignValidDTO
was removed in favor ofcreateValidDTO(...).signed(...)
.
Since 2.0.0 we support role-based auth, described in detail here, and we changed how @GalaTransaction
, @Submit
, and @Evaluate
handle transactions.
The most important breaking change is that all methods that update the chain need to be signed.
We no longer support writing to the chain without a signature.
Please, ensure that all methods that update the chain are signed (@GalaTransaction
has verifySignature: true
property, or @Submit
, or @Evaluate
is used, which enforce signature verification).
Additionally, we deprecated the allowedOrgs
property in the transaction decorators in favor of allowedRoles
.
We encourage users to migrate to the new approach.
As a backward compatibility measure, we still support allowedOrgs
for now, but we don't allow combining both allowedOrgs
and allowedRoles
within the same decorator.
The migration steps from allowedOrgs
to allowedRoles
are as follows:
- Verify which users should have access to the method, and what roles they should have.
For instance, if you have a curator-like organization for privileged access, like
allowedOrgs: ["CuratorOrg"]
, you may want to designate aCURATOR
role and assign it to the applicable users. - Assign roles to the users, using the
PublicKeyContract:UpdateUserRoles
method. You need to provide the full list of roles for the user, as the method replaces the existing roles with the new ones. - Ensure that your custom client code for user registration assigns the correct roles.
Registration methods from the
PublicKeyContract
assignEVALUATE
andSUBMIT
as default roles, but you may want additional ones. - Update the contract method decorators to use
allowedRoles
instead ofallowedOrgs
.
For most users, you probably won't need to do anything, as the default roles are EVALUATE
and SUBMIT
.
These roles alone are sufficient for calling methods with no allowedRoles
specified.
You may want to consult our acceptance test for migration from allowedOrgs
to allowedRoles
. For more details, see PublicKeyContract.migration.spec.ts.
We dropped support for the legacy authentication mechanism based on the Certificate Authority (CA) identity.
We now require all transactions that either (1) need to get the current user (ctx.callingUser
), or (2) need to verify the DTO signature, to be signed with the user's private key.
This is a breaking change, as the old way of calling methods without a signature is no longer supported.
It also means that:
- DER signatures with no additional data (
signerPublicKey
orsignerAddress
) are no longer supported, since they do not allow recovery of the public key from the signature. - Some endpoints that were previously available without a signature defaulted to setting the CA username as the value of
ctx.callingUser
. These endpoints now require a signature or an explicit user parameter (for instanceGetPublicKey
,FetchBalances
,FetchAllowances
).
Starting from version 2.0.0
:
- Test data for
users
contains full user objects with roles, not just user aliases (identity keys). - User field
identityKey
was renamed toalias
for clarity. - We recommend using signature-based authentication and authorization for all transactions, which requires signing the DTOs with the user's private key.
Sample usage of fixture
for testing transactions:
Using the previous version (1.x.x
):
const { ctx, contract, writes } = fixture(GalaChainTokenContract)
.callingUser(users.testUser1Id)
.savedState(nftClass, nftInstance, balance);
const dto = await createValidDTO(LockTokenDto, { ... });
const response = await contract.LockToken(ctx, dto);
expect(writes).toEqual(expectedWrites);
After upgrade to 2.0.0
:
const { ctx, contract, getWrites } = fixture(GalaChainTokenContract)
.registeredUsers(users.testUser1)
.savedState(nftClass, nftInstance, balance);
const dto = await createValidSubmitDTO(LockTokenDto, { ... })
.signed(users.testUser1.privateKey);
const response = await contract.LockToken(ctx, dto);
expect(getWrites()).toEqual(expectedWrites);
The main difference between the old CA-based auth and the new signature-based auth is that instead of using the user's CA (Certificate Authority) identity, we use the user's public key that is recovered from the DTO (Data Transfer Object) and the signature. We use the public key as a unique identifier of the user.
In the previous flow we were using callingUser
to set the user, and the user was identified by the CA identity.
In the new flow we use registeredUsers
to save relevant UserProfile
objects in the mocked chain state, and the user is authenticated on the basis of the signature.
This is why in the code for the new version we use signed
to sign the DTO with the user's private key.
The callingUser()
function is still supported, but it should be reserved for low-level stuff, when you don't call contract methods directly.
The parameter it takes is the user's object, as opposed to the alias, which was used previously.
It is done this way on purpose to allow you to detect if you need to update the code for the new version.
Additionally, the writes
property was changed to allWrites
, and we recommend developers use a new getWrites()
function.
It returns the writes in the form of a dictionary, but skips low-level writes that are not relevant for the test.
By default, getWrites()
skips the keys of UniqueTransaction
objects, which are used for enforcing the uniqueKey
field in the DTOs.
This behavior can be altered by providing custom skipKeysStartingWith: string[]
parameters.
Starting from version 2.0.0
we enforce the uniqueKey
field in each DTO that is used in @Submit
or @GalaTransaction({ type: SUBMIT })
methods.
This prevents duplicate transactions from being submitted to the chain, and protects against replay attacks.
Any time you submit a DTO with a missing uniqueKey
, the SDK will throw a validation error.
When you submit a DTO with a uniqueKey
that was already used, the SDK will throw a conflict error.
It is recommended to use the @Submit
decorator for all transactions that update the chain, as it enforces both the signature verification and the uniqueKey
field in the DTOs. This is accomplished by enforcing SubmitCallDTO
as the parent DTO class.
In order to make the creation of the uniqueKey
easier, we added an additional method, aside from createValidDTO
, for instantiating DTOs with uniqueKey
properties.
The new method (createValidSubmitDTO
) uses a similar syntax:
const dto = await createValidSubmitDTO(DtoClass, { ... }).signed(...);
Additionally, we added a utility method for creating a unique key, which can be used in the DTOs:
const uniqueKey = createUniqueKey();
Note that both createValidSubmitDTO
and createUniqueKey
make use of node:crypto
under the hood, which may not be supported in some environments.
We unified the API of the TokenBalance
class, which is used for storing the token balances in the chain state.
Previously, the methods of TokenBalance
class separated validation and mutation of the balance, which led to verbose syntax (e.g. balance.ensureCanAddInstance(...).add()
).
Now, the methods are unified, and the validation is done in the relevant methods (e.g. balance.addInstance(...)
).
In this way, we prefer clean code over SRP (Single Responsibility Principle) and we unify the API with different classes that are used for the chain state.
The change impacts chaincode code only.
Starting from version 2.0.0
we enforce strong validation for user related fields in the DTOs and chain objects.
There are two types of user related fields:
- User alias (
UserAlias
): A unique identifier for each user that is stored in objects saved on chain and processed within the majority of methods. Each chain user has a single unique alias (likeclient|abc
oreth|<checksumed-addr>
). - User reference (
UserRef
): A reference to the user object, used in the DTOs. A user reference unambiguously identifies the user, but a single user can have multiple references (for instanceclient|abc
, checksumed eth address, and lower-cased eth address can all refer to the same user).
Respectively, we have two new validation/serialization decorators: @IsUserAlias
and @IsUserRef
, and functions for casting and validation: asValidUserAlias
, and asValidUserRef
.
UserRef
can be resolved to UserAlias
using the resolveUserAlias
function.
This is useful when, for instance, you have an ethereum address, and you want to resolve it to the user alias of the registered user.
Prior to version 2.0.0
, user related fields were string fields with no additional validation.
This change primarily affects the DTOs, the chain objects that have user related fields, and the methods that use them.
Repeat the structure for each major release, documenting the breaking changes specific to that version. Update this document on each PR that makes breaking changes.
- Description of the deprecated features that have been removed in this release.
- Guidance on alternatives or migration paths for affected users.
- Description of any changes to existing API contracts that may impact users.
- Guidance on updating code to accommodate the new API.
- List of updated dependencies and any potential compatibility issues.
- Instructions for resolving dependency conflicts or compatibility issues.
- Any additional breaking changes not covered in the above categories.
- Guidance on how to address these changes in user code.