From 242978df300757e384e9956d99454ed9e33f79f0 Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Wed, 16 Apr 2025 12:06:19 +0900 Subject: [PATCH 1/3] refactor!: support of wip `rc.2` (nfts, empty blocks, config endpoints) Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- .github/workflows/pull-request.yml | 2 +- deno.lock | 9 ++ etc/__snapshots__/codegen_test.ts.snap | 118 ++++++++++++++++---- etc/codegen.ts | 29 +++++ etc/iroha-build-utils.ts | 13 +-- etc/task-codegen.ts | 28 +---- etc/task-prep-iroha.ts | 5 +- packages/client/api.ts | 55 ++++++++- packages/client/deno.jsonc | 2 +- packages/client/mod.ts | 19 +--- packages/core/data-model/compound.ts | 116 +++++++++++++++++-- packages/core/data-model/mod.ts | 12 +- packages/core/deno.jsonc | 2 +- packages/core/mod.ts | 1 + packages/core/query-internal.test.ts | 5 - packages/core/query-types.test-d.ts | 3 +- packages/core/tests/misc.test.ts | 33 +++--- tests/browser/cypress/e2e/main.cy.ts | 6 +- tests/node/tests/client-apis.spec.ts | 53 ++++++--- tests/node/tests/client-misc.spec.ts | 136 ++++++++++++----------- tests/node/tests/codec-compat.spec.ts | 14 +-- tests/node/tests/util.ts | 33 +++--- tests/support/test-configuration/node.ts | 4 +- tests/support/test-peer/mod.ts | 2 +- 24 files changed, 474 insertions(+), 226 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index de3bfc20..e7f499ce 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -6,7 +6,7 @@ on: branches: [main] env: IROHA_GIT: https://github.com/hyperledger-iroha/iroha.git - IROHA_REV: daa2d50fbe288aed1231c6f45c853698435477d6 + IROHA_REV: c1d8b5e2fbffabba13e448a66f9b5dd86875cf18 # 2.0.0-rc.2 wip jobs: prep-crypto-wasm: runs-on: ubuntu-latest diff --git a/deno.lock b/deno.lock index e13f7b49..dd1c3dcd 100644 --- a/deno.lock +++ b/deno.lock @@ -5,6 +5,7 @@ "jsr:@david/path@0.2": "0.2.0", "jsr:@david/which@~0.4.1": "0.4.1", "jsr:@dprint/formatter@*": "0.4.1", + "jsr:@iroha/core@0.3.1": "0.3.1", "jsr:@luca/cases@*": "1.0.0", "jsr:@std/assert@0.221": "0.221.0", "jsr:@std/assert@^1.0.10": "1.0.11", @@ -103,6 +104,14 @@ "@dprint/formatter@0.4.1": { "integrity": "96449ab83aa9f72df98caa5030d3f4a921c5c29d9b0e0d0da83d79e2024a9637" }, + "@iroha/core@0.3.1": { + "integrity": "ca1ed1d249b70a09bf7f558a868107a34d3c312fa3f3b76d0e1f584e3716b11f", + "dependencies": [ + "jsr:@std/assert@^1.0.11", + "jsr:@std/encoding@^1.0.7", + "npm:@scale-codec/core" + ] + }, "@luca/cases@1.0.0": { "integrity": "b5f9471f1830595e63a2b7d62821ac822a19e16899e6584799be63f17a1fbc30" }, diff --git a/etc/__snapshots__/codegen_test.ts.snap b/etc/__snapshots__/codegen_test.ts.snap index 31b6db00..273762f9 100644 --- a/etc/__snapshots__/codegen_test.ts.snap +++ b/etc/__snapshots__/codegen_test.ts.snap @@ -6,8 +6,9 @@ snapshot[`generate prototypes > prototypes snapshot 1`] = ` export type QueryCompatibleSelectors = { FindDomains: 'domain' | 'domain-id' | 'domain-id-name' | 'domain-metadata' | 'domain-metadata-key' FindAccounts: 'account' | 'account-id' | 'account-id-domain' | 'account-id-domain-name' | 'account-id-signatory' | 'account-metadata' | 'account-metadata-key' - FindAssets: 'asset' | 'asset-id' | 'asset-id-account' | 'asset-id-account-domain' | 'asset-id-account-domain-name' | 'asset-id-account-signatory' | 'asset-id-definition' | 'asset-id-definition-domain' | 'asset-id-definition-domain-name' | 'asset-id-definition-name' | 'asset-value' | 'asset-value-numeric' | 'asset-value-store' | 'asset-value-store-key' + FindAssets: 'asset' | 'asset-id' | 'asset-id-account' | 'asset-id-account-domain' | 'asset-id-account-domain-name' | 'asset-id-account-signatory' | 'asset-id-definition' | 'asset-id-definition-domain' | 'asset-id-definition-domain-name' | 'asset-id-definition-name' | 'asset-value' FindAssetsDefinitions: 'asset-definition' | 'asset-definition-id' | 'asset-definition-id-domain' | 'asset-definition-id-domain-name' | 'asset-definition-id-name' | 'asset-definition-metadata' | 'asset-definition-metadata-key' + FindNfts: 'nft' | 'nft-id' | 'nft-id-domain' | 'nft-id-domain-name' | 'nft-id-name' | 'nft-metadata' | 'nft-metadata-key' | 'nft-account-id' | 'nft-account-id-domain' | 'nft-account-id-domain-name' | 'nft-account-id-signatory' FindRoles: 'role' | 'role-id' | 'role-id-name' FindRoleIds: 'role-id' | 'role-id-name' FindPermissionsByAccountId: 'permission' @@ -44,10 +45,7 @@ export type SelectorIdToOutput = { 'asset-id-definition-domain': lib.DomainId 'asset-id-definition-domain-name': lib.Name 'asset-id-definition-name': lib.Name - 'asset-value': lib.AssetValue - 'asset-value-numeric': lib.Numeric - 'asset-value-store': lib.Metadata - 'asset-value-store-key': lib.Json + 'asset-value': lib.Numeric 'asset-definition': lib.AssetDefinition 'asset-definition-id': lib.AssetDefinitionId 'asset-definition-id-domain': lib.DomainId @@ -55,6 +53,17 @@ export type SelectorIdToOutput = { 'asset-definition-id-name': lib.Name 'asset-definition-metadata': lib.Metadata 'asset-definition-metadata-key': lib.Json + 'nft': lib.Nft + 'nft-id': lib.NftId + 'nft-id-domain': lib.DomainId + 'nft-id-domain-name': lib.Name + 'nft-id-name': lib.Name + 'nft-metadata': lib.Metadata + 'nft-metadata-key': lib.Json + 'nft-account-id': lib.AccountId + 'nft-account-id-domain': lib.DomainId + 'nft-account-id-domain-name': lib.Name + 'nft-account-id-signatory': lib.PublicKey 'role': lib.Role 'role-id': lib.RoleId 'role-id-name': lib.Name @@ -151,15 +160,6 @@ export type QuerySelectors = { } value: { __selector: 'asset-value', - numeric: { - __selector: 'asset-value-numeric', - } - store: { - __selector: 'asset-value-store', - key(key: lib.Name): { - __selector: 'asset-value-store-key', - } - } } } FindAssetsDefinitions: { @@ -183,6 +183,39 @@ export type QuerySelectors = { } } } + FindNfts: { + __selector: 'nft', + id: { + __selector: 'nft-id', + domain: { + __selector: 'nft-id-domain', + name: { + __selector: 'nft-id-domain-name', + } + } + name: { + __selector: 'nft-id-name', + } + } + metadata: { + __selector: 'nft-metadata', + key(key: lib.Name): { + __selector: 'nft-metadata-key', + } + } + accountId: { + __selector: 'nft-account-id', + domain: { + __selector: 'nft-account-id-domain', + name: { + __selector: 'nft-account-id-domain-name', + } + } + signatory: { + __selector: 'nft-account-id-signatory', + } + } + } FindRoles: { __selector: 'role', id: { @@ -378,16 +411,7 @@ export type QueryPredicates = { } } } - value: { - isNumeric: () => lib.AssetProjectionPredicate - isStore: () => lib.AssetProjectionPredicate - numeric: never - store: { - key: (key: lib.Name) => { - equals: (value: lib.Json) => lib.AssetProjectionPredicate - } - } - } + value: never } FindAssetsDefinitions: { id: { @@ -414,6 +438,46 @@ export type QueryPredicates = { } } } + FindNfts: { + id: { + equals: (value: lib.NftId) => lib.NftProjectionPredicate + domain: { + equals: (value: lib.DomainId) => lib.NftProjectionPredicate + name: { + equals: (value: lib.String) => lib.NftProjectionPredicate + contains: (value: lib.String) => lib.NftProjectionPredicate + startsWith: (value: lib.String) => lib.NftProjectionPredicate + endsWith: (value: lib.String) => lib.NftProjectionPredicate + } + } + name: { + equals: (value: lib.String) => lib.NftProjectionPredicate + contains: (value: lib.String) => lib.NftProjectionPredicate + startsWith: (value: lib.String) => lib.NftProjectionPredicate + endsWith: (value: lib.String) => lib.NftProjectionPredicate + } + } + metadata: { + key: (key: lib.Name) => { + equals: (value: lib.Json) => lib.NftProjectionPredicate + } + } + accountId: { + equals: (value: lib.AccountId) => lib.NftProjectionPredicate + domain: { + equals: (value: lib.DomainId) => lib.NftProjectionPredicate + name: { + equals: (value: lib.String) => lib.NftProjectionPredicate + contains: (value: lib.String) => lib.NftProjectionPredicate + startsWith: (value: lib.String) => lib.NftProjectionPredicate + endsWith: (value: lib.String) => lib.NftProjectionPredicate + } + } + signatory: { + equals: (value: lib.PublicKey) => lib.NftProjectionPredicate + } + } + } FindRoles: { id: { equals: (value: lib.RoleId) => lib.RoleProjectionPredicate @@ -527,6 +591,7 @@ export type QueryPredicates = { } } FindBlocks: { + isEmpty: () => lib.SignedBlockProjectionPredicate header: { hash: { equals: (value: lib.Hash) => lib.SignedBlockProjectionPredicate @@ -568,6 +633,11 @@ export class FindAPI { return new client.QueryBuilder(this._executor, 'FindAssetsDefinitions', params) } + /** Convenience method for \`FindNfts\` query, a variant of {@linkcode types.QueryBox} enum. */ + public nfts(params?: core.QueryBuilderParams): client.QueryBuilder<'FindNfts'> { + return new client.QueryBuilder(this._executor, 'FindNfts', params) + } + /** Convenience method for \`FindRoles\` query, a variant of {@linkcode types.QueryBox} enum. */ public roles(params?: core.QueryBuilderParams): client.QueryBuilder<'FindRoles'> { return new client.QueryBuilder(this._executor, 'FindRoles', params) diff --git a/etc/codegen.ts b/etc/codegen.ts index 823753fd..50eda893 100644 --- a/etc/codegen.ts +++ b/etc/codegen.ts @@ -150,12 +150,14 @@ export type LibType = | 'Bool' | 'Timestamp' | 'Duration' + | 'DurationCompact' | 'Name' | 'CompoundPredicate' | 'DomainId' | `AccountId` | `AssetDefinitionId` | `AssetId` + | 'NftId' | 'Algorithm' | 'Signature' | 'Hash' @@ -250,6 +252,7 @@ export class Resolver { 'DomainId', 'AccountId', 'AssetId', + 'NftId', 'AssetDefinitionId', 'Compact', 'Algorithm', @@ -279,6 +282,21 @@ export class Resolver { }), }), ) + .with( + { refStr: 'Uptime', schema: { Tuple: ['Compact', 'u32'] } }, + ({ refStr }) => ({ + t: 'local', + id: refStr, + // TODO: merge with duration? change Status in schema? + emit: () => ({ + t: 'struct', + fields: [{ name: 'secs', type: { t: 'lib', id: 'Compact' } }, { + name: 'nanos', + type: { t: 'lib', id: 'U32' }, + }], + }), + }), + ) .with( { ref: { id: P.union('Register', 'Unregister'), items: [P._] }, @@ -721,7 +739,18 @@ export class Resolver { params: [{ t: 'lib', id: 'NonZero', params: [{ t: 'lib', id: rewriteWith }] }], }), ) + .with( + [ + 'Duration', + { t: 'lib', id: 'Compact' }, + ], + () => ({ + t: 'lib', + id: 'DurationCompact', + }), + ) .otherwise(() => { + console.debug(this.resolve(x.type)) throw new Error(`Unexpected type of a field with _ms suffix: ${x.type}`) }) return { name: x.name.slice(0, -3), type } diff --git a/etc/iroha-build-utils.ts b/etc/iroha-build-utils.ts index fb009a3d..5be82ace 100644 --- a/etc/iroha-build-utils.ts +++ b/etc/iroha-build-utils.ts @@ -17,12 +17,11 @@ const resolvePrepIroha = (...paths: string[]) => { return path.resolve(dirname, '../prep/iroha', ...paths) } -export type Binary = 'irohad' | 'kagami' | 'iroha_codec' +export type Binary = 'irohad' | 'kagami' export const BIN_PATHS: Record = { irohad: resolvePrepIroha('irohad'), kagami: resolvePrepIroha('kagami'), - iroha_codec: resolvePrepIroha('iroha_codec'), } export const EXECUTOR_WASM_PATH: string = resolvePrepIroha('executor.wasm') @@ -36,14 +35,14 @@ for (const filePath of [...Object.values(BIN_PATHS), EXECUTOR_WASM_PATH]) { } } -export async function irohaCodecToScale( +export async function kagamiCodecToScale( type: keyof typeof SCHEMA, json: JsonValue, ): Promise { const input = JSON.stringify(json, undefined, 2) return new Promise((resolve, reject) => { - const child = spawn(BIN_PATHS.iroha_codec, ['json-to-scale', '--type', type], { + const child = spawn(BIN_PATHS.kagami, ['codec', 'json-to-scale', '--type', type], { stdio: ['pipe', 'pipe', 'inherit'], }) @@ -54,7 +53,7 @@ export async function irohaCodecToScale( }) child.on('close', (code) => { - if (code !== 0) reject(new Error('non-zero exit code of iroha_codec')) + if (code !== 0) reject(new Error('non-zero exit code of kagami')) resolve(Uint8Array.from(Buffer.concat(chunks))) }) @@ -63,12 +62,12 @@ export async function irohaCodecToScale( }) } -export async function irohaCodecToJson( +export async function kagamiCodecToJson( type: keyof typeof SCHEMA, scale: Uint8Array, ): Promise { return new Promise((resolve, reject) => { - const child = spawn(BIN_PATHS.iroha_codec, ['scale-to-json', '--type', type], { + const child = spawn(BIN_PATHS.kagami, ['codec', 'scale-to-json', '--type', type], { stdio: ['pipe', 'pipe', 'inherit'], }) diff --git a/etc/task-codegen.ts b/etc/task-codegen.ts index 79fafb5f..0a589b96 100644 --- a/etc/task-codegen.ts +++ b/etc/task-codegen.ts @@ -1,7 +1,5 @@ -import type { Schema } from '@iroha/core/data-model/schema' import SCHEMA from '@iroha/core/data-model/schema-json' import { generateClientFindAPI, generateDataModel, generatePrototypes, Resolver } from './codegen.ts' -import { expect } from '@std/expect' import * as colors from '@std/fmt/colors' import { parseArgs } from 'jsr:@std/cli/parse-args' import { assertEquals } from '@std/assert/equals' @@ -41,31 +39,7 @@ async function writeAll(entries: { file: string; code: () => string }[]) { } } -/** - * There are not included into the schema for some reason, but are useful to generate code for. - */ -const EXTENSION: Schema = { - Status: { - Struct: [ - { name: 'peers', type: 'Compact' }, - { name: 'blocks', type: 'Compact' }, - { name: 'txs_accepted', type: 'Compact' }, - { name: 'txs_rejected', type: 'Compact' }, - { name: 'uptime', type: 'Uptime' }, - { name: 'view_changes', type: 'Compact' }, - { name: 'queue_size', type: 'Compact' }, - ], - }, - Uptime: { - Struct: [ - { name: 'secs', type: 'Compact' }, - { name: 'nanos', type: 'u32' }, - ], - }, -} -expect(Object.keys(SCHEMA)).not.toContain(Object.keys(EXTENSION)) - -const resolver = new Resolver({ ...SCHEMA, ...EXTENSION }) +const resolver = new Resolver(SCHEMA) console.time('codegen') await writeAll([ diff --git a/etc/task-prep-iroha.ts b/etc/task-prep-iroha.ts index 96d80979..f31e9af0 100644 --- a/etc/task-prep-iroha.ts +++ b/etc/task-prep-iroha.ts @@ -69,7 +69,7 @@ async function cloneRepo(repo: string, tagOrRevision: string) { } async function buildBinaries() { - const binaries = ['irohad', 'iroha_kagami', 'iroha_codec'] + const binaries = ['irohad', 'iroha_kagami'] $.logStep('Building binaries:', binaries) const args = binaries.map((x) => ['-p', x]) await $`cargo build --release ${args}`.cwd(IROHA_REPO_DIR) @@ -95,7 +95,6 @@ async function copyFromRepoToPrep() { for ( const artifactPath of [ 'target/release/irohad', - 'target/release/iroha_codec', 'target/release/kagami', 'defaults/executor.wasm', 'docs/source/references/schema.json', @@ -116,7 +115,7 @@ async function artifactsReady(): Promise { } assertEquals( files, - new Set(['irohad', 'iroha_codec', 'kagami', 'executor.wasm', 'schema.json']), + new Set(['irohad', 'kagami', 'executor.wasm', 'schema.json']), 'all artifacts must be present', ) return true diff --git a/packages/client/api.ts b/packages/client/api.ts index 602b4519..dd4f50bf 100644 --- a/packages/client/api.ts +++ b/packages/client/api.ts @@ -14,6 +14,7 @@ import { HEALTHY_RESPONSE, } from './const.ts' import { urlJoinPath } from './util.ts' +import type { PartialDeep } from 'type-fest' /** * Peer information returned from {@link TelemetryAPI.peers} @@ -29,12 +30,45 @@ export interface PeerJson { id: dm.PublicKey } -export interface PeerConfig { +export type PeerGetConfig = { logger: { level: dm.Level['kind'] + /** + * Filter directives, e.g. `info,iroha_core=debug`. + */ + filter: string + } + network: { + blockGossipPeriod: dm.Duration + blockGossipSize: number + transactionGossipPeriod: dm.Duration + transactionGossipSize: number + } + publicKey: dm.PublicKey + queue: { + capacity: number } } +type PeerGetConfigRaw = { + logger: { + level: dm.Level['kind'] + filter: string + } + network: { + block_gossip_period_ms: number + block_gossip_size: number + transaction_gossip_period_ms: number + transaction_gossip_size: number + } + public_key: string + queue: { + capacity: number + } +} + +export type PeerSetConfig = PartialDeep> + export type Fetch = typeof fetch export type HealthResult = VariantUnit<'healthy'> | Variant<'error', unknown> @@ -136,13 +170,25 @@ export class MainAPI { .then(handleQueryResponse) } - public async getConfig(): Promise { + public async getConfig(): Promise { const response = await this.http.getFetch()(urlJoinPath(this.http.toriiBaseURL, ENDPOINT_CONFIGURATION)) await ResponseError.assertStatus(response, 200) - return response.json() + // TODO: use schema parser e.g. zod? + const raw: PeerGetConfigRaw = await response.json() + return { + publicKey: dm.PublicKey.fromMultihash(raw.public_key), + logger: raw.logger, + network: { + blockGossipSize: raw.network.block_gossip_size, + blockGossipPeriod: dm.Duration.fromMillis(raw.network.block_gossip_period_ms), + transactionGossipSize: raw.network.transaction_gossip_size, + transactionGossipPeriod: dm.Duration.fromMillis(raw.network.transaction_gossip_period_ms), + }, + queue: raw.queue, + } } - public async setConfig(config: PeerConfig): Promise { + public async setConfig(config: PeerSetConfig): Promise { const response = await this.http.getFetch()(urlJoinPath(this.http.toriiBaseURL, ENDPOINT_CONFIGURATION), { method: 'POST', body: JSON.stringify(config), @@ -202,6 +248,7 @@ export class TelemetryAPI { return response.arrayBuffer().then((buffer) => getCodec(dm.Status).decode(new Uint8Array(buffer))) } + // TODO: move once metrics are updated public async peers(): Promise { const response = await this.http.getFetch()(urlJoinPath(this.http.toriiBaseURL, ENDPOINT_PEERS)) await ResponseError.assertStatus(response, 200) diff --git a/packages/client/deno.jsonc b/packages/client/deno.jsonc index 8e531b13..6ba382a2 100644 --- a/packages/client/deno.jsonc +++ b/packages/client/deno.jsonc @@ -1,6 +1,6 @@ { "name": "@iroha/client", - "version": "0.3.0", + "version": "0.4.0-beta.1", "exports": { ".": "./mod.ts", "./web-socket": "./web-socket/mod.ts" diff --git a/packages/client/mod.ts b/packages/client/mod.ts index e4bbe9c8..8fcee5f0 100644 --- a/packages/client/mod.ts +++ b/packages/client/mod.ts @@ -64,7 +64,7 @@ * * const newAsset: types.NewAssetDefinition = { * id: new types.AssetDefinitionId(new types.Name('time'), newDomain.id), - * type: types.AssetType.Numeric({ scale: 1 }), + * spec: { scale: 1 }, * mintable: types.Mintable.Infinitely, * logo: null, * metadata: [], @@ -118,19 +118,10 @@ * } * * for (const asset of await client.find.assets().executeAll()) { - * console.log('Asset', asset.id.toString()) - * if (asset.value.kind === 'Numeric') { - * console.log(' Numeric:', asset.value.value.mantissa, asset.value.value.scale) - * } else { - * for (const { key, value } of asset.value.value) { - * console.log(` Metadata: key="${key.value}" value=${value.asJsonString()}`) - * } - * } - * // => Asset rose##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland - * // => Numeric: 42n 0n - * // => Asset registry#wonderland#ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@looking_glass - * // => Metadata: key="foo" value=['foo', 'bar'] - * // => Metadata: key="bar" value={"whatever":"whichever"} + * console.log('Asset:', asset.id.toString()) + * console.log('Asset value:', asset.value.mantissa, asset.value.scale) + * // => Asset: rose##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland + * // => Asset value: 42n 0n * // ... * } * } diff --git a/packages/core/data-model/compound.ts b/packages/core/data-model/compound.ts index f28bb0f1..76567f92 100644 --- a/packages/core/data-model/compound.ts +++ b/packages/core/data-model/compound.ts @@ -3,7 +3,7 @@ import type { JsonValue } from 'type-fest' import { enumCodec, type GenCodec, getCodec, lazyCodec, structCodec, SYMBOL_CODEC } from '../codec.ts' import { type IsZero, type Ord, ordCompare } from '../traits.ts' import type { Variant } from '../util.ts' -import { String, U64, Vec } from './primitives.ts' +import { Compact, String, U64, Vec } from './primitives.ts' // TODO document that parse/stringify json lazily when needed export class Json implements Ord { @@ -95,6 +95,9 @@ export class Timestamp { export { Timestamp as TimestampU128 } +/** + * Convenience wrapper around `u64` numbers representing durations. + */ export class Duration implements IsZero { public static [SYMBOL_CODEC]: GenCodec = getCodec(U64).wrap({ fromBase: (x) => Duration.fromMillis(x), @@ -125,6 +128,39 @@ export class Duration implements IsZero { } } +/** + * Convenience wrapper around `Compact` integers representing durations. + */ +export class DurationCompact implements IsZero { + public static [SYMBOL_CODEC]: GenCodec = getCodec(Compact).wrap({ + fromBase: (x) => DurationCompact.fromMillis(x), + toBase: (y) => y.asMillis(), + }) + + public static fromMillis(ms: number | bigint): DurationCompact { + return new DurationCompact(BigInt(ms)) + } + + private readonly _ms: bigint + + protected constructor(ms: bigint) { + if (ms < 0n) throw new TypeError(`Duration could not be negative, got: ${ms}`) + this._ms = ms + } + + public asMillis(): bigint { + return this._ms + } + + public isZero(): boolean { + return this._ms === 0n + } + + public toJSON(): { ms: bigint } { + return { ms: this._ms } + } +} + export type CompoundPredicate = | Variant<'Atom', Atom> | Variant<'Not', CompoundPredicate> @@ -206,8 +242,8 @@ export class Name implements Ord { if (/[\s#@]/.test(name)) { throw new SyntaxError( `Invalid name: "${name}". Name should not contain whitespace characters, ` + - `'@' (reserved for '⟨signatory⟩@⟨domain⟩' constructs, e.g. 'ed....@wonderland'), ` + - `and '#' (reserved for '⟨asset⟩#⟨domain⟩' constructs, e.g. 'rose#wonderland') `, + `"@" (reserved for "⟨signatory⟩@⟨domain⟩" constructs, e.g. "ed....@wonderland"), ` + + `and "#" (reserved for "⟨asset⟩#⟨domain⟩" constructs, e.g. "rose#wonderland") `, ) } @@ -250,6 +286,7 @@ export class AccountId implements Ord { public readonly signatory: crypto.PublicKey public readonly domain: DomainId + private __brand!: 'AccountId' public constructor(signatory: crypto.PublicKey, domain: DomainId) { this.signatory = signatory @@ -281,7 +318,7 @@ function accountIdFromObj({ signatory, domain }: { signatory: string; domain: st function accountIdFromStr(str: string): AccountId { const parts = str.split('@') if (parts.length !== 2) { - throw new SyntaxError(`AccountId should have format '⟨signatory⟩@⟨domain⟩, got: '${str}'`) + throw new SyntaxError(`AccountId should have format "⟨signatory⟩@⟨domain⟩", got: "${str}"`) } const [signatory, domain] = parts return accountIdFromObj({ signatory, domain }) @@ -310,6 +347,7 @@ export class AssetDefinitionId { public readonly name: Name public readonly domain: DomainId + private __brand!: 'AssetDefinitionId' public constructor(name: Name, domain: DomainId) { this.name = name @@ -336,7 +374,7 @@ function assetDefIdFromStr(input: string) { const parts = input.split('#') if (parts.length !== 2) { throw new SyntaxError( - `AssetDefinitionId should have format '⟨name⟩#⟨domain⟩, e.g. 'rose#wonderland', got '${input}'`, + `AssetDefinitionId should have format "⟨name⟩#⟨domain⟩", e.g. "rose#wonderland", got "${input}"`, ) } const [name, domain] = parts @@ -369,6 +407,7 @@ export class AssetId { public readonly account: AccountId public readonly definition: AssetDefinitionId + private __brand!: 'AssetId' public constructor(account: AccountId, definition: AssetDefinitionId) { this.account = account @@ -393,8 +432,8 @@ function assetIdFromStr(input: string) { const match = input.match(/^(.+)#(.+)?#(.+)@(.+)$/) if (!match) { throw new SyntaxError( - `AssetId should have format '⟨name⟩#⟨asset domain⟩#⟨account signatory⟩@⟨account domain⟩' ` + - `or '⟨name⟩##⟨account signatory⟩@⟨account domain⟩' (when asset and account domains are the same), got '${input}'`, + `AssetId should have format "⟨name⟩#⟨asset domain⟩#⟨account signatory⟩@⟨account domain⟩" ` + + `or "⟨name⟩##⟨account signatory⟩@⟨account domain⟩" (when asset and account domains are the same), got "${input}"`, ) } const [, asset, domain1, account, domain2] = match @@ -403,3 +442,66 @@ function assetIdFromStr(input: string) { assetDefIdFromObj({ name: asset, domain: domain1 ?? domain2 }), ) } + +/** + * Identification of Non-Fungible Token (asset). + */ +export class NftId { + public static [SYMBOL_CODEC]: GenCodec = structCodec(['domain', 'name'], { + domain: getCodec(DomainId), + name: getCodec(Name), + }).wrap({ toBase: (x) => x, fromBase: (x) => new NftId(x.name, x.domain) }) + + /** + * Parse NFT ID from its string representation. + * + * @example + * ```ts + * import { assertEquals } from '@std/assert' + * + * const id = NftId.parse('nft$domain') + * + * assertEquals(id.name.value, 'nft') + * assertEquals(id.domain.value, 'domain') + * ``` + */ + public static parse(str: string): NftId { + const parts = str.split('$') + if (parts.length !== 2) { + throw new SyntaxError( + `NfdId should have format "⟨name⟩$⟨domain⟩", e.g. "nft$domain", got "${str}"`, + ) + } + const [name, domain] = parts + return new NftId(new Name(name), new DomainId(domain)) + } + + public readonly domain: DomainId + public readonly name: Name + private __brand!: 'NftId' + + public constructor(name: Name, domain: DomainId) { + this.domain = domain + this.name = name + } + + /** + * Returns string representation of NFT ID. + * + * @example + * ```ts + * import { assertEquals } from '@std/assert' + * + * const id = new NftId(new Name('nft'), new DomainId('domain')) + * + * assertEquals(id.toString(), 'nft$domain') + * ``` + */ + public toString(): string { + return `${this.name.value}$${this.domain.value}` + } + + public toJSON(): string { + return this.toString() + } +} diff --git a/packages/core/data-model/mod.ts b/packages/core/data-model/mod.ts index 453b32f8..03a45615 100644 --- a/packages/core/data-model/mod.ts +++ b/packages/core/data-model/mod.ts @@ -34,8 +34,8 @@ * ```ts * import * as types from '@iroha/core/data-model' * - * const store: types.AssetType = { kind: 'Store' } - * const numeric: types.AssetType = { kind: 'Numeric', value: { scale: 5 } } + * const repeats1: types.Repeats = { kind: 'Indefinitely' } + * const repeats2: types.Repeats = { kind: 'Exactly', value: 5 } * ``` * * Alternatively, enums could be constructed with pre-generated constructors, which makes it less verbose: @@ -44,11 +44,11 @@ * import * as types from '@iroha/core/data-model' * import { assertEquals } from '@std/assert/equals' * - * const store = types.AssetType.Store - * const numeric = types.AssetType.Numeric({ scale: 5 }) + * const repeats1 = types.Repeats.Indefinitely + * const repeats2 = types.Repeats.Exactly(5) * - * assertEquals(store, { kind: "Store" }) - * assertEquals(numeric, { kind: "Numeric", value: { scale: 5 } }) + * assertEquals(repeats1, { kind: 'Indefinitely' }) + * assertEquals(repeats2, { kind: 'Exactly', value: 5 }) * ``` * * Constructors approach is especially useful when it comes to enums nested into each other, e.g. {@link Parameter}: diff --git a/packages/core/deno.jsonc b/packages/core/deno.jsonc index c7092326..99bf00e7 100644 --- a/packages/core/deno.jsonc +++ b/packages/core/deno.jsonc @@ -1,6 +1,6 @@ { "name": "@iroha/core", - "version": "0.3.1", + "version": "0.4.0-beta.1", "exports": { ".": "./mod.ts", "./codec": "./codec.ts", diff --git a/packages/core/mod.ts b/packages/core/mod.ts index d9f4023c..decbacd7 100644 --- a/packages/core/mod.ts +++ b/packages/core/mod.ts @@ -16,6 +16,7 @@ * * | Iroha | `@iroha/core` | * | --: | :-- | + * | `2.0.0-rc.2.x` (wip) | `0.4.x` (wip) | * | `2.0.0-rc.1.x` | `0.3.x`, `0.2.x`, ~~`0.1.0`~~ ([broken](https://github.com/hyperledger-iroha/iroha-javascript/issues/210#issuecomment-2662231135)) | * | `2.0.0-pre-rc.20.x` and before | not supported, use the **Legacy SDK** | * diff --git a/packages/core/query-internal.test.ts b/packages/core/query-internal.test.ts index 901c2249..3c7c88f2 100644 --- a/packages/core/query-internal.test.ts +++ b/packages/core/query-internal.test.ts @@ -53,11 +53,6 @@ describe('predicateProto()', () => { proto.id.account.signatory.equals(SAMPLE_ACC.signatory), dm.AssetProjectionPredicate.Id.Account.Signatory.Atom.Equals(SAMPLE_ACC.signatory), ) - - compare( - proto.value.isNumeric(), - dm.AssetProjectionPredicate.Value.Atom.IsNumeric, - ) }) test('FindTransactions', () => { diff --git a/packages/core/query-types.test-d.ts b/packages/core/query-types.test-d.ts index d6af3dfd..99357cc3 100644 --- a/packages/core/query-types.test-d.ts +++ b/packages/core/query-types.test-d.ts @@ -66,8 +66,7 @@ const filterAllKinds = new QueryBuilder('FindAssets').filterWith((asset) => CompoundPredicate.Atom(asset.id.definition.name.contains('test')), ), ), - CompoundPredicate.Atom(asset.value.isStore()), - CompoundPredicate.Atom(asset.value.store.key(new types.Name('test')).equals(types.Json.fromValue([false, true]))), + CompoundPredicate.Atom(asset.id.definition.name.startsWith('test')), ) ) diff --git a/packages/core/tests/misc.test.ts b/packages/core/tests/misc.test.ts index 185ac78e..89269a20 100644 --- a/packages/core/tests/misc.test.ts +++ b/packages/core/tests/misc.test.ts @@ -6,6 +6,7 @@ import * as dm from '@iroha/core/data-model' import { getCodec } from '@iroha/core' import { fromHexWithSpaces, SAMPLE_ACCOUNT_ID } from './util.ts' import { Bytes } from '../crypto/util.ts' +import { decodeHex, encodeHex } from '@std/encoding/hex' function jsonSerDe(value: unknown): unknown { return JSON.parse(JSON.stringify(value)) @@ -89,7 +90,7 @@ describe('JSON/string representations', () => { test('Fails to parse account id with multiple @', () => { expect(() => dm.AccountId.parse('a@b@c')).toThrow( new SyntaxError( - `AccountId should have format '⟨signatory⟩@⟨domain⟩, got: 'a@b@c'`, + `AccountId should have format "⟨signatory⟩@⟨domain⟩", got: "a@b@c"`, ), ) }) @@ -111,7 +112,8 @@ describe('Status', () => { const STATUS: dm.Status = { peers: 4n, blocks: 5n, - txsAccepted: 31n, + blocksNonEmpty: 3n, + txsApproved: 31n, txsRejected: 3n, uptime: { secs: 5n, @@ -120,25 +122,26 @@ describe('Status', () => { viewChanges: 2n, queueSize: 18n, } - const ENCODED = '10 14 7C 0C 14 40 7C D9 37 08 48' + const ENCODED = '10140C7C0C14407CD9370848' - expect(getCodec(dm.Status).encode(STATUS)).toEqual(fromHexWithSpaces(ENCODED)) - expect(getCodec(dm.Status).decode(fromHexWithSpaces(ENCODED))).toEqual(STATUS) + expect(encodeHex(getCodec(dm.Status).encode(STATUS)).toUpperCase()).toEqual(ENCODED) + expect(getCodec(dm.Status).decode(decodeHex(ENCODED))).toEqual(STATUS) }) test('From zeros', () => { - expect(getCodec(dm.Status).decode(fromHexWithSpaces('00 00 00 00 00 00 00 00 00 00 00'))).toEqual( + expect(getCodec(dm.Status).decode(fromHexWithSpaces('00 00 00 00 00 00 00 00 00 00 00 00'))).toEqual( { - 'blocks': 0n, - 'peers': 0n, - 'queueSize': 0n, - 'txsAccepted': 0n, - 'txsRejected': 0n, - 'uptime': { - 'nanos': 0, - 'secs': 0n, + blocks: 0n, + blocksNonEmpty: 0n, + peers: 0n, + queueSize: 0n, + txsApproved: 0n, + txsRejected: 0n, + uptime: { + nanos: 0, + secs: 0n, }, - 'viewChanges': 0n, + viewChanges: 0n, }, ) }) diff --git a/tests/browser/cypress/e2e/main.cy.ts b/tests/browser/cypress/e2e/main.cy.ts index 5ebb8787..f8d774a6 100644 --- a/tests/browser/cypress/e2e/main.cy.ts +++ b/tests/browser/cypress/e2e/main.cy.ts @@ -7,7 +7,7 @@ it('Register new domain and wait until commitment', () => { cy.visit('/') // wait for the genesis block - cy.get('h3').contains('Status').closest('div').contains('Blocks: 1') + cy.get('h3').contains('Status').closest('div').contains('Blocks: 2') cy.get('button').contains('Listen').click().contains('Stop') @@ -15,8 +15,8 @@ it('Register new domain and wait until commitment', () => { cy.get('button').contains('Register domain').click() // Ensure that block count is incremented - cy.contains('Blocks: 2') + cy.contains('Blocks: 4') // And all events are caught - cy.get('ul.events-list').children('li').should('have.length', 6).last().contains('Block').contains('Applied') + cy.get('ul.events-list').children('li').should('have.length', 10).last().contains('Block').contains('Applied') }) diff --git a/tests/node/tests/client-apis.spec.ts b/tests/node/tests/client-apis.spec.ts index c338bd66..c01b28cc 100644 --- a/tests/node/tests/client-apis.spec.ts +++ b/tests/node/tests/client-apis.spec.ts @@ -21,23 +21,43 @@ describe('Various API methods', () => { describe('configuration', () => { test('update and retrieve', async () => { - const { client } = await usePeer() + const { client } = await usePeer({ seed: 'config-test' }) let config = await client.api.getConfig() - expect(config.logger.level).toBe('debug') + expect(config).toMatchInlineSnapshot(` + { + "logger": { + "filter": null, + "level": "DEBUG", + }, + "network": { + "blockGossipPeriod": { + "ms": 10000n, + }, + "blockGossipSize": 4, + "transactionGossipPeriod": { + "ms": 1000n, + }, + "transactionGossipSize": 500, + }, + "publicKey": "ed0120ADEF9FC53F7705DCD2C0059097470B15A961B16C94EA4A4D01AB0B7E5DD6F91B", + "queue": { + "capacity": 65536, + }, + } + `) await client.api.setConfig({ logger: { level: 'TRACE' } }) config = await client.api.getConfig() - expect(config.logger.level).toBe('trace') + expect(config.logger.level).toBe('TRACE') }) - // FIXME: re-enable after fix of https://github.com/hyperledger-iroha/iroha/issues/5247 - test.skip('throws an error when trying to set an invalid configuration', async () => { + test('throws an error when trying to set an invalid configuration', async () => { const { client } = await usePeer() await expect(client.api.setConfig({ logger: { level: 'TR' as any } })).rejects.toThrowErrorMatchingInlineSnapshot( - `[Error: 400: Bad Request]`, + `[Error: 422 (Unprocessable Entity): Failed to deserialize the JSON body into the target type: logger.level: unknown variant \`TR\`, expected one of \`TRACE\`, \`DEBUG\`, \`INFO\`, \`WARN\`, \`ERROR\` at line 1 column 23]`, ) }) }) @@ -58,7 +78,7 @@ describe('Telemetry API methods', () => { const metrics = await client.api.telemetry.metrics() // just some line from Prometheus metrics - expect(metrics).toMatch('block_height 1') + expect(metrics).toMatch('block_height 2') }) test('status', async () => { @@ -67,15 +87,16 @@ describe('Telemetry API methods', () => { const { uptime, ...rest } = await client.api.telemetry.status() expect(rest).toMatchInlineSnapshot(` - { - "blocks": 1n, - "peers": 0n, - "queueSize": 0n, - "txsAccepted": 3n, - "txsRejected": 0n, - "viewChanges": 0n, - } - `) + { + "blocks": 2n, + "blocksNonEmpty": 1n, + "peers": 0n, + "queueSize": 0n, + "txsApproved": 3n, + "txsRejected": 0n, + "viewChanges": 0n, + } + `) expect(uptime).toEqual( expect.objectContaining({ nanos: expect.any(Number), diff --git a/tests/node/tests/client-misc.spec.ts b/tests/node/tests/client-misc.spec.ts index 8fb698e2..c7eabb0c 100644 --- a/tests/node/tests/client-misc.spec.ts +++ b/tests/node/tests/client-misc.spec.ts @@ -3,17 +3,20 @@ import { describe, expect, test } from 'vitest' import { Bytes, KeyPair } from '@iroha/core/crypto' import { blockHash } from '@iroha/core' import * as dm from '@iroha/core/data-model' -import { type Client, QueryValidationError } from '@iroha/client' +import type { Client } from '@iroha/client' import { usePeer } from './util.ts' -import { match, P } from 'ts-pattern' import { ACCOUNT_KEY_PAIR, DOMAIN } from '@iroha/test-configuration' +/** + * TODO: describe structure, re-shape it + */ async function submitTestData(client: Client) { const bob = KeyPair.deriveFromSeed(Bytes.hex('bbbb')) const bobAcc = new dm.AccountId(bob.publicKey(), new dm.DomainId('based')) const bobPub = bob.publicKey().multihash() const madHatter = KeyPair.deriveFromSeed(Bytes.hex('aaaa')) + const madHatterAcc = new dm.AccountId(madHatter.publicKey(), new dm.Name('certainty')) const EMPTY_LOGO_META = { logo: null, metadata: [] } satisfies Pick @@ -33,25 +36,20 @@ async function submitTestData(client: Client) { metadata: [{ key: new dm.Name('alias'), value: dm.Json.fromValue('Bob') }], }), dm.InstructionBox.Register.Account({ - id: new dm.AccountId(madHatter.publicKey(), new dm.Name('certainty')), + id: madHatterAcc, metadata: [{ key: new dm.Name('alias'), value: dm.Json.fromValue('Mad Hatter') }], }), - dm.InstructionBox.Register.AssetDefinition({ - id: dm.AssetDefinitionId.parse('base_coin#based'), - type: dm.AssetType.Store, - mintable: dm.Mintable.Not, - ...EMPTY_LOGO_META, + dm.InstructionBox.Register.Nft({ + id: dm.NftId.parse('base_coin$based'), + content: [], }), - dm.InstructionBox.Register.AssetDefinition({ - id: dm.AssetDefinitionId.parse('neko_coin#wherever'), - type: dm.AssetType.Store, - mintable: dm.Mintable.Not, - logo: null, - metadata: [{ key: new dm.Name('foo'), value: dm.Json.fromValue(['bar', false]) }], + dm.InstructionBox.Register.Nft({ + id: dm.NftId.parse('neko_coin$wherever'), + content: [{ key: new dm.Name('foo'), value: dm.Json.fromValue(['bar', false]) }], }), dm.InstructionBox.Register.AssetDefinition({ id: dm.AssetDefinitionId.parse('gator_coin#certainty'), - type: dm.AssetType.Numeric({ scale: null }), + spec: { scale: null }, mintable: dm.Mintable.Infinitely, ...EMPTY_LOGO_META, }), @@ -69,23 +67,33 @@ async function submitTestData(client: Client) { destination: dm.AssetId.parse(`gator_coin##${madHatter.publicKey().multihash()}@certainty`), }), - dm.InstructionBox.SetKeyValue.Asset({ - object: dm.AssetId.parse(`neko_coin#wherever#${bobAcc}`), + dm.InstructionBox.SetKeyValue.Nft({ + object: dm.NftId.parse(`neko_coin$wherever`), key: new dm.Name('mewo?'), value: dm.Json.fromValue({ me: 'wo' }), }), - dm.InstructionBox.SetKeyValue.Asset({ - object: dm.AssetId.parse(`base_coin#based#${madHatter.publicKey().multihash()}@certainty`), + dm.InstructionBox.Transfer.Nft({ + object: dm.NftId.parse('neko_coin$wherever'), + source: client.authority, + destination: bobAcc, + }), + dm.InstructionBox.SetKeyValue.Nft({ + object: dm.NftId.parse(`base_coin$based`), key: new dm.Name('hey'), value: dm.Json.fromValue([1, 2, 3]), }), + dm.InstructionBox.Transfer.Nft({ + object: dm.NftId.parse('base_coin$based'), + source: client.authority, + destination: madHatterAcc, + }), dm.InstructionBox.Register.Trigger({ // TODO: make just Name id: new dm.Name('mewo_maker'), action: { filter: dm.EventFilterBox.Data.Asset({ idMatcher: null, - eventSet: new Set(['MetadataInserted', 'MetadataRemoved', 'Created', 'Deleted', 'Added', 'Removed']), + eventSet: new Set(['Created', 'Deleted', 'Added', 'Removed']), }), executable: dm.Executable.Instructions([dm.InstructionBox.Log({ level: { kind: 'WARN' }, msg: 'MEWO!' })]), repeats: dm.Repeats.Indefinitely, @@ -165,12 +173,12 @@ describe('Queries', () => { const items = await client.find .accountsWithAsset( { - assetDefinition: dm.AssetDefinitionId.parse('neko_coin#wherever'), + assetDefinition: dm.AssetDefinitionId.parse('gator_coin#certainty'), }, ).selectWith((acc) => acc.metadata.key(new dm.Name('alias'))) .executeAll() - expect(items.map((x) => x.asValue())).toEqual(['Bob']) + expect(items.map((x) => x.asValue())).toEqual(['Bob', 'Mad Hatter']) }) test('find blocks and block headers', async () => { @@ -189,7 +197,7 @@ describe('Queries', () => { const txs = await client.find.transactions().executeAll() const status = await client.api.telemetry.status() - expect(txs).toHaveLength(Number(status.txsAccepted)) + expect(txs).toHaveLength(Number(status.txsApproved + status.txsRejected)) }) test('find triggers', async () => { @@ -208,17 +216,17 @@ describe('Queries', () => { `) }) - test('filter assets by the ending of its definition name', async () => { + test('filter nfts by containing string in its name', async () => { const { client } = await usePeer() await submitTestData(client) - const items: dm.Name[] = await client.find - .assets() - .filterWith((asset) => dm.CompoundPredicate.Atom(asset.id.definition.name.endsWith('_coin'))) - .selectWith((asset) => asset.id.definition.name) - .executeAll() + const items: dm.Name = await client.find + .nfts() + .filterWith((nft) => dm.CompoundPredicate.Atom(nft.id.name.contains('neko'))) + .selectWith((nft) => nft.id.name) + .executeSingle() - expect(items.map((x) => x.value)).contain.all.members(['base_coin', 'neko_coin', 'gator_coin']) + expect(items.value).toEqual('neko_coin') }) test('FAIL returns nothing, PASS returns all', async () => { @@ -297,29 +305,48 @@ describe('Queries', () => { const { client } = await usePeer() await submitTestData(client) - const unsorted = await client.find.assets().executeAll() - const sorted = await client.find.assets({ sorting: { byMetadataKey: new dm.Name('mewo?') } }).executeAll() + const unsorted = await client.find.nfts().executeAll() + const sorted = await client.find.nfts({ sorting: { byMetadataKey: new dm.Name('mewo?') } }) + .executeAll() expect(new Set(sorted)).toEqual(new Set(unsorted)) expect(sorted).not.toEqual(unsorted) - expect( - sorted.map((asset) => - match(asset) - .with( - { value: { kind: 'Store', value: P.array({ key: { value: 'mewo?' }, value: P.select() }) } }, - ([val]) => val, - ) - .otherwise(() => null) - ), - ).toMatchInlineSnapshot(` + expect(sorted).toMatchInlineSnapshot(` [ { - "me": "wo", + "content": [ + { + "key": "foo", + "value": [ + "bar", + false, + ], + }, + { + "key": "mewo?", + "value": { + "me": "wo", + }, + }, + ], + "id": "neko_coin$wherever", + "ownedBy": "ed0120B6F3A798AA75B19102B0B2F5F675B1248D5DB7ADD770EB9684FE5ED19014F9F2@based", + }, + { + "content": [ + { + "key": "hey", + "value": [ + 1, + 2, + 3, + ], + }, + ], + "id": "base_coin$based", + "ownedBy": "ed01205DEE97BBA67AE39F87D94D3C66E4E4701685D483BCFF2657B44DF40B06DBDA71@certainty", }, - null, - null, - null, ] `) }) @@ -363,7 +390,7 @@ describe('Queries', () => { asset.id.definition.equals(dm.AssetDefinitionId.parse('gator_coin#certainty')), ) ) - .selectWith((asset) => [asset.id.account, asset.value.numeric]) + .selectWith((asset) => [asset.id.account, asset.value]) .executeAll() expect(assets).toMatchInlineSnapshot(` @@ -385,21 +412,6 @@ describe('Queries', () => { ] `) }) - - test('when numeric assets filtered, but store is selected, validation error happens and passed', async () => { - const { client } = await usePeer() - await submitTestData(client) - - await expect( - client.find - .assets() - .filterWith((asset) => dm.CompoundPredicate.Atom(asset.value.isNumeric())) - .selectWith((asset) => asset.value.store) - .executeAll(), - ).rejects.toEqual( - new QueryValidationError(dm.ValidationFail.QueryFailed.Conversion('Expected store value, got numeric')), - ) - }) }) describe('Singular queries', () => { diff --git a/tests/node/tests/codec-compat.spec.ts b/tests/node/tests/codec-compat.spec.ts index 70440520..f78c0b30 100644 --- a/tests/node/tests/codec-compat.spec.ts +++ b/tests/node/tests/codec-compat.spec.ts @@ -2,7 +2,7 @@ import type { Except, JsonValue } from 'type-fest' import { type CodecContainer, defineCodec, getCodec } from '@iroha/core/codec' import * as dm from '@iroha/core/data-model' import type SCHEMA from '@iroha/core/data-model/schema-json' -import { irohaCodecToScale } from 'iroha-build-utils' +import { kagamiCodecToScale } from 'iroha-build-utils' import { describe, expect, test } from 'vitest' import { Bytes, KeyPair } from '@iroha/core/crypto' @@ -308,18 +308,18 @@ test.each([ }), // TODO: add SignedBlock ] as Case[])( - `Check encoding against iroha_codec of type $type: $value`, + `Check encoding against kagami codec of type $type: $value`, async (data: Case) => { - const referenceEncoded = await irohaCodecToScale(data.type, data.json) + const referenceEncoded = await kagamiCodecToScale(data.type, data.json) const actualEncoded = getCodec(data.codec).encode(data.value) expect(toHex(actualEncoded)).toEqual(toHex(referenceEncoded)) }, ) describe('BTree{Set/Map}', () => { - test('Metadata encoding matches with iroha_codec', async () => { + test('Metadata encoding matches with kagami codec', async () => { const CODEC = getCodec(dm.Metadata) - const reference = await irohaCodecToScale('Metadata', { + const reference = await kagamiCodecToScale('Metadata', { foo: 'bar', bar: [1, 2, 3], '1': 2, @@ -373,7 +373,7 @@ describe('BTree{Set/Map}', () => { ) => [new dm.AccountId(key, domain), new dm.AccountId(key, domain)]) ) - const reference = await irohaCodecToScale( + const reference = await kagamiCodecToScale( 'SortedVec', ids.map((x) => x.toJSON()), ) @@ -385,7 +385,7 @@ describe('BTree{Set/Map}', () => { test('BTreeSet - encoding matches', async () => { const codec = getCodec(dm.PermissionsSet) - const reference = await irohaCodecToScale('SortedVec', [ + const reference = await kagamiCodecToScale('SortedVec', [ { name: 'foo', payload: [1, 2, 3] }, { name: 'foo', payload: [3, 2, 1] }, { name: 'bar', payload: false }, diff --git a/tests/node/tests/util.ts b/tests/node/tests/util.ts index c75e252f..694f1aa3 100644 --- a/tests/node/tests/util.ts +++ b/tests/node/tests/util.ts @@ -7,15 +7,6 @@ import uniquePort from 'get-port' import { ACCOUNT_KEY_PAIR, CHAIN, DOMAIN } from '@iroha/test-configuration' import { createGenesis } from '@iroha/test-configuration/node' import * as TestPeer from '@iroha/test-peer' -import { delay } from '@std/async' - -async function waitForGenesisCommitted(f: () => Promise) { - while (true) { - const { blocks } = await f() - if (blocks >= 1) return - await delay(50) - } -} async function uniquePortsPair() { return { @@ -24,22 +15,30 @@ async function uniquePortsPair() { } } -export async function usePeer(): Promise<{ client: Client }> { +export type SeedParams = { + seed?: Uint8Array | string +} + +export async function usePeer(params?: SeedParams): Promise<{ client: Client }> { const { peers: [peer], - } = await useNetwork({ peers: 1 }) + } = await useNetwork({ peers: 1, ...params }) return peer } -export async function useNetwork(params: { - peers: number - seed?: Uint8Array -}): Promise<{ peers: { client: Client; keypair: KeyPair; ports: { api: number; p2p: number } }[] }> { +export async function useNetwork( + params: { + peers: number + } & SeedParams, +): Promise<{ peers: { client: Client; keypair: KeyPair; ports: { api: number; p2p: number } }[] }> { const configs = await Promise.all( Array.from({ length: params.peers }, async (_v, i) => { + const seed = params.seed + ? typeof params.seed === 'string' ? new TextEncoder().encode(params.seed) : [...params.seed] + : [] const key = params.seed - ? KeyPair.deriveFromSeed(Bytes.array(new Uint8Array([...params.seed, i, i, i]))) + ? KeyPair.deriveFromSeed(Bytes.array(new Uint8Array([...seed, i, i, i]))) : KeyPair.random() return { key, ports: await uniquePortsPair() } }), @@ -69,8 +68,6 @@ export async function useNetwork(params: { chain: CHAIN, }) - await waitForGenesisCommitted(() => client.api.telemetry.status()) - return { keypair: key, ports, client } }), ) diff --git a/tests/support/test-configuration/node.ts b/tests/support/test-configuration/node.ts index 6a925264..0e4af4f4 100644 --- a/tests/support/test-configuration/node.ts +++ b/tests/support/test-configuration/node.ts @@ -3,7 +3,7 @@ import { getCodec } from '@iroha/core' import fs from 'node:fs/promises' import path from 'node:path' import { temporaryDirectory } from 'tempy' -import { BIN_PATHS, EXECUTOR_WASM_PATH, irohaCodecToJson } from 'iroha-build-utils' +import { BIN_PATHS, EXECUTOR_WASM_PATH, kagamiCodecToJson } from 'iroha-build-utils' import type { PublicKey } from '@iroha/core/crypto' import { ACCOUNT_KEY_PAIR, CHAIN, GENESIS_KEY_PAIR } from './mod.ts' import { spawn } from 'node:child_process' @@ -23,7 +23,7 @@ export async function createGenesis(params: { const alice = dm.AccountId.parse(`${ACCOUNT_KEY_PAIR.publicKey().multihash()}@${DOMAIN.value}`) const genesis = dm.AccountId.parse(`${GENESIS_KEY_PAIR.publicKey().multihash()}@genesis`) - const instructionsJson = await irohaCodecToJson( + const instructionsJson = await kagamiCodecToJson( 'Vec', dm.Vec.with(getCodec(dm.InstructionBox)).encode([ dm.InstructionBox.Register.Domain({ id: DOMAIN, metadata: [], logo: null }), diff --git a/tests/support/test-peer/mod.ts b/tests/support/test-peer/mod.ts index f4f37d24..67543ac3 100644 --- a/tests/support/test-peer/mod.ts +++ b/tests/support/test-peer/mod.ts @@ -40,7 +40,7 @@ async function waitForGenesis(url: URL, abort: AbortSignal) { } try { - const { blocks } = await new MainAPI(new HttpTransport(url)).telemetry.status() + const { blocksNonEmpty: blocks } = await new MainAPI(new HttpTransport(url)).telemetry.status() if (blocks === 1n) break throw `blocks: ${blocks}` } catch (error) { From b3bfdfa8b14949177e1903080dcdd416ee8c0c58 Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Wed, 16 Apr 2025 12:10:54 +0900 Subject: [PATCH 2/3] chore: revert versions Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- packages/client/deno.jsonc | 2 +- packages/core/deno.jsonc | 2 +- packages/core/mod.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client/deno.jsonc b/packages/client/deno.jsonc index 6ba382a2..8e531b13 100644 --- a/packages/client/deno.jsonc +++ b/packages/client/deno.jsonc @@ -1,6 +1,6 @@ { "name": "@iroha/client", - "version": "0.4.0-beta.1", + "version": "0.3.0", "exports": { ".": "./mod.ts", "./web-socket": "./web-socket/mod.ts" diff --git a/packages/core/deno.jsonc b/packages/core/deno.jsonc index 99bf00e7..c7092326 100644 --- a/packages/core/deno.jsonc +++ b/packages/core/deno.jsonc @@ -1,6 +1,6 @@ { "name": "@iroha/core", - "version": "0.4.0-beta.1", + "version": "0.3.1", "exports": { ".": "./mod.ts", "./codec": "./codec.ts", diff --git a/packages/core/mod.ts b/packages/core/mod.ts index decbacd7..d0ae442c 100644 --- a/packages/core/mod.ts +++ b/packages/core/mod.ts @@ -16,7 +16,7 @@ * * | Iroha | `@iroha/core` | * | --: | :-- | - * | `2.0.0-rc.2.x` (wip) | `0.4.x` (wip) | + * | `2.0.0-rc.2.x` | `0.4.x` | * | `2.0.0-rc.1.x` | `0.3.x`, `0.2.x`, ~~`0.1.0`~~ ([broken](https://github.com/hyperledger-iroha/iroha-javascript/issues/210#issuecomment-2662231135)) | * | `2.0.0-pre-rc.20.x` and before | not supported, use the **Legacy SDK** | * From af0bc2a5868515a953f91b088529b693ddf8b40e Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Wed, 16 Apr 2025 13:25:37 +0900 Subject: [PATCH 3/3] ci: remove extra script Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- .github/workflows/pull-request.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index e7f499ce..fcc604f4 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -62,7 +62,6 @@ jobs: if: steps.prep-cache.outputs.cache-hit != 'true' run: | deno task prep:iroha --git $IROHA_GIT --git-rev $IROHA_REV - deno task prep:iroha:build check-only: if: github.event_name == 'pull_request'