Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

web SDK v8 #385

Merged
merged 29 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a2f852b
common: wasmToSchema: Base custom section resolution on module versio…
bisgardo Apr 26, 2024
f6ca2f0
Update concordium-base reference
soerenbf Aug 27, 2024
affcd78
Align `TransactionType` enum with the corresponding type from
soerenbf Aug 27, 2024
26c3517
Update `AccountInfo` with cooldown fields
soerenbf Aug 27, 2024
3174697
Translation of updated grpc types
soerenbf Aug 28, 2024
18a4bb9
Merge branch 'main' into release/web-sdk/8
soerenbf Aug 28, 2024
ca952cd
Revert "Align `TransactionType` enum with the corresponding type from"
soerenbf Aug 28, 2024
ececf67
Merge branch 'release/web-sdk/8' into pv7-conformance
soerenbf Aug 28, 2024
0073323
Fix changelog
soerenbf Aug 28, 2024
8c44117
Merge branch 'release/web-sdk/8' into pv7-conformance
soerenbf Aug 28, 2024
865b614
Update changelog with p7 conformance changes
soerenbf Aug 28, 2024
ea4d193
Bump version
soerenbf Aug 28, 2024
5e85ac5
Merge branch 'release/web-sdk/8' into pv7-conformance
soerenbf Aug 28, 2024
e75709b
Bump version
soerenbf Aug 28, 2024
45ccafc
Merge branch 'release/web-sdk/8' into pv7-conformance
soerenbf Aug 28, 2024
80f4bf9
Added new transaction events
soerenbf Aug 28, 2024
377c93e
Fix examples build
soerenbf Aug 28, 2024
3c5b92b
Fix build fail
soerenbf Aug 28, 2024
26b9fbe
Set available balance for client test data
soerenbf Aug 28, 2024
1e4bbb6
Remove credID equality tests
soerenbf Aug 28, 2024
508ec9f
Merge pull request #384 from Concordium/pv7-conformance
soerenbf Aug 29, 2024
ba63393
Bump versions of depending packages
soerenbf Aug 29, 2024
24fa230
Merge branch 'pv7-conformance' into release/web-sdk/8
soerenbf Aug 29, 2024
a76f5b9
Alpha version bump
soerenbf Sep 6, 2024
c4c54fe
Add fallback for available balance to be compatible with node version 6
soerenbf Sep 6, 2024
d2e5a42
Merge pull request #389 from Concordium/node-6-compat
soerenbf Sep 6, 2024
f762398
Version bump
soerenbf Sep 9, 2024
df090d5
Non-blocking wasm compilation
soerenbf Sep 10, 2024
b262717
Remove node version requirement from readme
soerenbf Sep 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion examples/nodejs/client/getEmbeddedSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ const client = new ConcordiumGRPCNodeClient(address, Number(port), credentials.c
const schema = await client.getEmbeddedSchema(moduleRef);
// #endregion documentation-snippet

fs.writeFileSync(cli.flags.outPath, schema);
if (schema) {
fs.writeFileSync(cli.flags.outPath, new Uint8Array(schema.buffer));
}

console.log('Wrote schema to file: ', cli.flags.outPath);
})();
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ const client = new ConcordiumGRPCNodeClient(address, Number(port), credentials.c
const schema = await client.getEmbeddedSchema(moduleRef);
// #endregion documentation-snippet

fs.writeFileSync(cli.flags.outPath, schema);
if (schema) {
fs.writeFileSync(cli.flags.outPath, new Uint8Array(schema.buffer));
}

console.log('Wrote schema to file: ', cli.flags.outPath);
})();
6 changes: 3 additions & 3 deletions examples/nodejs/composed-examples/initAndUpdateContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const client = new ConcordiumGRPCNodeClient(address, Number(port), credentials.c
sender,
};

const initParams = serializeInitContractParameters(contractName, sunnyWeather, schema);
const initParams = serializeInitContractParameters(contractName, sunnyWeather, schema!.buffer);

const initPayload: InitContractPayload = {
amount: CcdAmount.zero(),
Expand Down Expand Up @@ -142,7 +142,7 @@ const client = new ConcordiumGRPCNodeClient(address, Number(port), credentials.c
contractName,
EntrypointName.fromString('set'),
rainyWeather,
schema
schema!.buffer
);

const updatePayload: UpdateContractPayload = {
Expand Down Expand Up @@ -187,7 +187,7 @@ const client = new ConcordiumGRPCNodeClient(address, Number(port), credentials.c
const rawReturnValue = unwrap(invokedPostInit.returnValue);
const returnValue = deserializeReceiveReturnValue(
ReturnValue.toBuffer(rawReturnValue),
schema,
schema!.buffer,
contractName,
EntrypointName.fromString('get')
);
Expand Down
2 changes: 1 addition & 1 deletion packages/ccd-js-gen/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ async function generateCode(
}
notifier.done('Parse smart contract module.');

const moduleSchema = rawModuleSchema === null ? null : SDK.parseRawModuleSchema(rawModuleSchema);
const moduleSchema = rawModuleSchema && SDK.parseRawModuleSchema(rawModuleSchema);

const outputFilePath = path.format({
dir: outDirPath,
Expand Down
12 changes: 12 additions & 0 deletions packages/sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 8.0.0

### Breaking changes
soerenbf marked this conversation as resolved.
Show resolved Hide resolved

- `getEmbeddedModuleSchema` now uses the module version to determine in which custom wasm sections to look for the schema.
It also no longer is `async` because there isn't any need for it to be so.
soerenbf marked this conversation as resolved.
Show resolved Hide resolved
- `ConcordiumGRPCClient.getEmbeddedSchema` now delegates to `getEmbeddedModuleSchema` instead of `wasmToSchema`
(which was removed as it was just a less capable version of `getEmbeddedModuleSchema`).
This means that it returns the complete `RawModuleSchema` instead of only the schema bytes.
It also means that it returns `null` instead of an error when no embedded schema was found.


## 7.5.1

### Fixed
Expand Down
9 changes: 5 additions & 4 deletions packages/sdk/src/GenericContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,10 +521,11 @@ export class Contract<E extends string = string, V extends string = string> exte
if (!schema) {
try {
const raw = await grpcClient.getEmbeddedSchema(instanceInfo.sourceModule);
const encoded = Buffer.from(raw).toString('base64');

if (encoded) {
mSchema = encoded;
if (raw) {
const encoded = Buffer.from(raw.buffer).toString('base64');
if (encoded) {
mSchema = encoded;
}
}
} catch {
// Do nothing.
Expand Down
19 changes: 12 additions & 7 deletions packages/sdk/src/grpc/GRPCClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { calculateEnergyCost } from '../energyCost.js';
import { HealthClient } from '../grpc-api/v2/concordium/health.client.js';
import { QueriesClient } from '../grpc-api/v2/concordium/service.client.js';
import * as v2 from '../grpc-api/v2/concordium/types.js';
import { RawModuleSchema } from '../schemaTypes.js';
import { serializeAccountTransactionPayload } from '../serialization.js';
import * as v1 from '../types.js';
import { HexString, isRpcError } from '../types.js';
Expand All @@ -29,8 +30,9 @@ import * as SequenceNumber from '../types/SequenceNumber.js';
import * as Timestamp from '../types/Timestamp.js';
import * as TransactionExpiry from '../types/TransactionExpiry.js';
import * as TransactionHash from '../types/TransactionHash.js';
import { getEmbeddedModuleSchema } from '../types/VersionedModuleSource.js';
import type { BlockItemStatus, BlockItemSummary } from '../types/blockItemSummary.js';
import { countSignatures, isHex, isValidIp, mapRecord, mapStream, unwrap, wasmToSchema } from '../util.js';
import { countSignatures, isHex, isValidIp, mapRecord, mapStream, unwrap } from '../util.js';
import * as translate from './translation.js';

/**
Expand Down Expand Up @@ -199,13 +201,16 @@ export class ConcordiumGRPCClient {
* @param moduleRef the module's reference, represented by the ModuleReference class.
* @param blockHash optional block hash to get the module embedded schema at, otherwise retrieves from last finalized block
*
* @returns the module schema as a buffer.
* @throws An error of type `RpcError` if not found in the block.
* @throws If the module or schema cannot be parsed
* @returns the module schema as a {@link RawModuleSchema} or `null` if not found in the block.
* @throws An error of type `RpcError` if the module was not found in the block.
* @throws If the module source cannot be parsed or contains duplicate schema sections.
*/
async getEmbeddedSchema(moduleRef: ModuleReference.Type, blockHash?: BlockHash.Type): Promise<Uint8Array> {
const versionedSource = await this.getModuleSource(moduleRef, blockHash);
return wasmToSchema(versionedSource.source);
async getEmbeddedSchema(
moduleRef: ModuleReference.Type,
blockHash?: BlockHash.Type
): Promise<RawModuleSchema | undefined> {
const source = await this.getModuleSource(moduleRef, blockHash);
return getEmbeddedModuleSchema(source);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/src/pub/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export * from '../signHelpers.js';
export * from '../versionedTypeHelpers.js';
export * from '../accountHelpers.js';

export { isHex, streamToList, wasmToSchema, unwrap } from '../util.js';
export { isHex, streamToList, unwrap } from '../util.js';

export * from '../accountTransactions.js';
export * from '../energyCost.js';
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/src/schemaTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function toOptionJson<T>(value: T | undefined): OptionJson<T> {
}

/** Schema version from before the schema bytes contained version information. */
type UnversionedSchemaVersion = 0 | 1;
export type UnversionedSchemaVersion = 0 | 1;

/**
* Represents unparsed bytes for a smart contract module schema.
Expand Down
55 changes: 41 additions & 14 deletions packages/sdk/src/types/VersionedModuleSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import { Buffer } from 'buffer/index.js';
import * as H from '../contractHelpers.js';
import { Cursor, deserializeUInt32BE } from '../deserializationHelpers.js';
import { sha256 } from '../hash.js';
import { RawModuleSchema } from '../schemaTypes.js';
import { RawModuleSchema, UnversionedSchemaVersion } from '../schemaTypes.js';
import { encodeWord32 } from '../serializationHelpers.js';
import { VersionedModuleSource } from '../types.js';
import { schemaBytesFromWasmModule } from '../util.js';
import * as ModuleReference from './ModuleReference.js';

/** Interface of a smart contract containing the name of the contract and every entrypoint. */
Expand Down Expand Up @@ -100,22 +99,50 @@ export async function parseModuleInterface(moduleSource: VersionedModuleSource):
* Extract the embedded smart contract schema bytes. Returns `null` if no schema is embedded.
* @param {VersionedModuleSource} moduleSource The smart contract module source.
* @returns {RawModuleSchema | null} The raw module schema if found.
* @throws If the module source cannot be parsed or contains duplicate schema sections.
*/
export async function getEmbeddedModuleSchema(moduleSource: VersionedModuleSource): Promise<RawModuleSchema | null> {
const wasmModule = await WebAssembly.compile(moduleSource.source);
const versionedSchema = schemaBytesFromWasmModule(wasmModule, 'concordium-schema');
if (versionedSchema !== null) {
return { type: 'versioned', buffer: versionedSchema };
export function getEmbeddedModuleSchema({ source, version }: VersionedModuleSource): RawModuleSchema | undefined {
const sections = findCustomSections(new WebAssembly.Module(source), version);
soerenbf marked this conversation as resolved.
Show resolved Hide resolved
if (sections === undefined) {
return undefined;
}
const { sectionName, unversionedSchemaVersion, contents } = sections;
if (contents.length !== 1) {
throw new Error(
`invalid module: expected to find at most one custom section named "${sectionName}", but found ${contents.length}`
);
}
const schema = contents[0];
if (unversionedSchemaVersion !== undefined) {
return {
type: 'unversioned',
version: unversionedSchemaVersion,
buffer: schema,
};
}
const unversionedSchemaV0 = schemaBytesFromWasmModule(wasmModule, 'concordium-schema-v1');
if (unversionedSchemaV0 !== null) {
return { type: 'unversioned', version: 0, buffer: unversionedSchemaV0 };
return { type: 'versioned', buffer: schema };
}

function findCustomSections(m: WebAssembly.Module, moduleVersion: number) {
function getCustomSections(sectionName: string, unversionedSchemaVersion: UnversionedSchemaVersion | undefined) {
const s = WebAssembly.Module.customSections(m, sectionName);
return s.length === 0 ? undefined : { sectionName, unversionedSchemaVersion, contents: s };
}
const unversionedSchemaV1 = schemaBytesFromWasmModule(wasmModule, 'concordium-schema-v2');
if (unversionedSchemaV1 !== null) {
return { type: 'unversioned', version: 1, buffer: unversionedSchemaV1 };

// First look for section containing schema with embedded version, then "-v1" or "-v2" depending on the module version.
switch (moduleVersion) {
case 0:
return (
getCustomSections('concordium-schema', undefined) || // always v0
getCustomSections('concordium-schema-v1', 0) // v0 (not a typo)
);
case 1:
return (
getCustomSections('concordium-schema', undefined) || // v1, v2, or v3
getCustomSections('concordium-schema-v2', 1) // v1 (not a typo)
);
}
return null;
return getCustomSections('concordium-schema', undefined); // expecting to find this section in future module versions
}

/**
Expand Down
39 changes: 0 additions & 39 deletions packages/sdk/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Buffer } from 'buffer/index.js';

import { AccountTransactionSignature, HexString, IpAddressString } from './types.js';
Expand Down Expand Up @@ -60,44 +59,6 @@ export function countSignatures(accountSignatures: AccountTransactionSignature):
return totalSignatureCount;
}

/**
* Compiles a wasm module and extracts the smart contract schema.
*
* @param wasm the wasm module as a Buffer
*
* @throws If WASM module contains no schema
* @throws If WASM module provided is invalid
*
* @returns the smart contract schema as a Buffer
*/
export function wasmToSchema(wasm: ArrayBuffer): Uint8Array {
const wasmModule = new WebAssembly.Module(wasm);
const schemaBytes = schemaBytesFromWasmModule(wasmModule, 'concordium-schema');
if (schemaBytes === null) {
throw Error('WASM-Module contains no schema!');
}
return new Uint8Array(schemaBytes);
}

/**
* Extracts custom-section containing the smart contract schema if present.
* @param wasmModule the WebAssembly module.
* @returns the smart contract schema as a Buffer or null if not present.
*/
export function schemaBytesFromWasmModule(
wasmModule: WebAssembly.Module,
sectionName: 'concordium-schema' | 'concordium-schema-v1' | 'concordium-schema-v2'
): ArrayBuffer | null {
const sections = WebAssembly.Module.customSections(wasmModule, sectionName);
if (sections.length === 1) {
return sections[0];
} else if (sections.length === 0) {
return null;
} else {
throw Error('Invalid WASM-Module retrieved!');
}
}

/**
* Convert a Date to seconds since epoch.
*/
Expand Down
16 changes: 8 additions & 8 deletions packages/sdk/test/ci/module-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ describe('VersionedModuleSource: getEmbeddedModuleSchema', () => {
path.join(testFileDir, 'cis2-wccd-embedded-schema-v1-versioned.wasm.v1')
);
const moduleSource = versionedModuleSourceFromBuffer(contractModule);
const moduleSchema = await getEmbeddedModuleSchema(moduleSource);
if (moduleSchema === null) {
const moduleSchema = getEmbeddedModuleSchema(moduleSource);
if (moduleSchema === undefined) {
fail('Failed to find module schame');
}
expect(moduleSchema.type).toBe('versioned');
Expand All @@ -24,8 +24,8 @@ describe('VersionedModuleSource: getEmbeddedModuleSchema', () => {
path.join(testFileDir, 'cis1-wccd-embedded-schema-v0-versioned.wasm.v0')
);
const moduleSource = versionedModuleSourceFromBuffer(contractModule);
const moduleSchema = await getEmbeddedModuleSchema(moduleSource);
if (moduleSchema === null) {
const moduleSchema = getEmbeddedModuleSchema(moduleSource);
if (moduleSchema === undefined) {
fail('Failed to find module schame');
}
expect(moduleSchema.type).toBe('versioned');
Expand All @@ -39,8 +39,8 @@ describe('VersionedModuleSource: getEmbeddedModuleSchema', () => {
version: 0,
source: Buffer.from(unversionedContractModule),
} as const;
const moduleSchema = await getEmbeddedModuleSchema(moduleSource);
if (moduleSchema === null) {
const moduleSchema = getEmbeddedModuleSchema(moduleSource);
if (moduleSchema === undefined) {
fail('Failed to find module schame');
}
expect(moduleSchema.type).toBe('unversioned');
Expand All @@ -51,8 +51,8 @@ describe('VersionedModuleSource: getEmbeddedModuleSchema', () => {
path.join(testFileDir, 'cis2-wccd-embedded-schema-v1-unversioned.wasm.v1')
);
const moduleSource = versionedModuleSourceFromBuffer(contractModule);
const moduleSchema = await getEmbeddedModuleSchema(moduleSource);
if (moduleSchema === null) {
const moduleSchema = getEmbeddedModuleSchema(moduleSource);
if (moduleSchema === undefined) {
fail('Failed to find module schame');
}
expect(moduleSchema.type).toBe('unversioned');
Expand Down
10 changes: 7 additions & 3 deletions packages/sdk/test/ci/util.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { readFileSync } from 'fs';

import { stringToInt, wasmToSchema } from '../../src/util.js';
import { getEmbeddedModuleSchema } from '../../src/index.js';
import { stringToInt } from '../../src/util.js';

test('stringToInt transforms chosen field, but not others', () => {
const keysToTransform = ['a'];
Expand Down Expand Up @@ -31,7 +32,10 @@ test('Embedded schema is the same as a seperate schema file', () => {
const wasmModule = versionedWasmModule.subarray(8);

const seperateSchema = readFileSync('test/ci/resources/icecream-schema.bin');
const embeddedSchema = wasmToSchema(wasmModule);
const embeddedSchema = getEmbeddedModuleSchema({
source: wasmModule,
version: 1,
});

expect(new Uint8Array(seperateSchema)).toEqual(embeddedSchema);
expect(new Uint8Array(seperateSchema)).toEqual(new Uint8Array(embeddedSchema!.buffer));
});
2 changes: 1 addition & 1 deletion packages/sdk/test/client/clientV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ test.each(clients)('getEmbeddedSchema', async (client) => {
const rawReturnValue = invoked.returnValue;
const returnValue = v1.deserializeReceiveReturnValue(
v1.ReturnValue.toBuffer(rawReturnValue),
schema,
schema!.buffer,
v1.ContractName.fromStringUnchecked('weather'),
v1.EntrypointName.fromStringUnchecked('get')
);
Expand Down
Loading