Lisk SDK v6を使ったブロックチェーンアプリの作り方 その5
はじめに
こんにちは万博おじです。
今回はモジュールのエンドポイントの作成編です。
前回の続きなので、見ていない方はそちらを先にどうぞ!
また、前回同様、この回で触れたファイルについては全内容を記事の最後に記載しておきますのでわからなくなったら参考にどうぞ。
モジュールのエンドポイントの作成
1. エンドポイントの作成
コマンドでオンチェーンストアに登録されたmessageを取得するエンドポイントを作成するため、src/app/modules/hello/endpoint.tsを以下のように編集します。
import { BaseEndpoint, ModuleEndpointContext, cryptography } from 'lisk-sdk';
import { MessageStore, MessageStoreData } from './stores/message';
export class HelloEndpoint extends BaseEndpoint {
public async getHello(ctx: ModuleEndpointContext): Promise<MessageStoreData> {
// 1. Get message store
const messageSubStore = this.stores.get(MessageStore);
// 2. Get the address from the endpoint params
const { address } = ctx.params;
// 3. Validate address
if (typeof address !== 'string') {
throw new Error('Parameter address must be a string.');
}
cryptography.address.validateLisk32Address(address);
// 4. Get the Hello message for the address from the message store
const helloMessage = await messageSubStore.get(
ctx,
cryptography.address.getAddressFromLisk32Address(address),
);
// 5. Return the Hello message
return helloMessage;
}
}
2. エンドポイント用のスキーマの作成
エンドポイントを実行する際のリクエストパラメータおよびエンドポイントの実行結果(レスポンス)を設定するため、src/app/modules/hello/schema.tsに以下を追加します。
export const getHelloRequestSchema = {
$id: 'modules/hello/endpoint/getHelloRequest',
type: 'object',
required: ['address'],
properties: {
address: {
type: 'string',
format: 'lisk32',
},
},
};
export const getHelloResponseSchema = {
$id: 'modules/hello/endpoint/getHello',
type: 'object',
required: ['message'],
properties: {
message: {
type: 'string',
format: 'utf8'
},
},
};
3. エンドポイントを使用するための設定
エンドポイントを作成しただけでは使用できないため、src/app/modules/hello/module.ts を以下のように編集します。
~記載省略~
import { configSchema, getHelloRequestSchema, getHelloResponseSchema } from './schema';
~記載省略~
public metadata(): ModuleMetadata {
return {
endpoints: [
{
name: this.endpoint.getHello.name,
request: getHelloRequestSchema,
response: getHelloResponseSchema,
},
],
commands: this.commands.map(command => ({
name: command.name,
params: command.schema,
})),
events: this.events.values().map(v => ({
name: v.name,
data: v.schema,
})),
assets: [],
stores: [],
};
}
~記載省略~
エンドポイントの実行
ここまで出来たらビルド後にブロックチェーンアプリを実行し、ダッシュボード(http://localhost:4005)を開きましょう。
アプリ実行は前回PM2の導入を行っている方は以下のコマンドです。
cd ~/hello
npm run build
pm2 start pm2_config.json
画面左上のCurrent heightが10秒ごとに増えていることを確認しましょう。
確認できたら、画面中央あたりにある「Call endpoint」を以下の画像のように設定し、Submitボタンを押しましょう。
Success!と言われ、コマンドを実行した際に設定したmessageが表示されたらOKです。
おわりに
今回はエンドポイントの作成をしましたがいかがでしたでしょうか?
次回はイベントとプラグインの作成あたりを予定していますが、正直なところ、ここまで出来れば最低限のブロックチェーンアプリが作成出来るようになったと言っても過言ではありません!
フロントエンドを作成すれば公開することも出来ると思います。
なんか作りたいなーと思っている人はとりあえずここまでを目標に頑張ってみてはいかがでしょうか?
ということでお疲れさまでした!
おまけ:今回触ったソースの全内容
src/app/modules/hello/endpoint.ts
import { BaseEndpoint, ModuleEndpointContext, cryptography } from 'lisk-sdk';
import { MessageStore, MessageStoreData } from './stores/message';
export class HelloEndpoint extends BaseEndpoint {
public async getHello(ctx: ModuleEndpointContext): Promise<MessageStoreData> {
// 1. Get message store
const messageSubStore = this.stores.get(MessageStore);
// 2. Get the address from the endpoint params
const { address } = ctx.params;
// 3. Validate address
if (typeof address !== 'string') {
throw new Error('Parameter address must be a string.');
}
cryptography.address.validateLisk32Address(address);
// 4. Get the Hello message for the address from the message store
const helloMessage = await messageSubStore.get(
ctx,
cryptography.address.getAddressFromLisk32Address(address),
);
// 5. Return the Hello message
return helloMessage;
}
}
src/app/modules/hello/schema.ts
export const configSchema = {
$id: '/hello/config',
type: 'object',
properties: {
maxMessageLength: {
type: 'integer',
format: 'uint32',
},
minMessageLength: {
type: 'integer',
format: 'uint32',
},
blacklist: {
type: 'array',
items: {
type: 'string',
minLength: 1,
maxLength: 40,
},
},
},
required: ['maxMessageLength', 'minMessageLength', 'blacklist'],
};
export const createHelloSchema = {
$id: 'hello/createHello-params',
title: 'CreateHelloCommand transaction parameter for the Hello module',
type: 'object',
required: ['message'],
properties: {
message: {
dataType: 'string',
fieldNumber: 1,
minLength: 3,
maxLength: 256,
},
},
};
export const getHelloRequestSchema = {
$id: 'modules/hello/endpoint/getHelloRequest',
type: 'object',
required: ['address'],
properties: {
address: {
type: 'string',
format: 'lisk32',
},
},
};
export const getHelloResponseSchema = {
$id: 'modules/hello/endpoint/getHello',
type: 'object',
required: ['message'],
properties: {
message: {
type: 'string',
format: 'utf8'
},
},
};
src/app/modules/hello/module.ts
/* eslint-disable class-methods-use-this */
/* eslint-disable @typescript-eslint/member-ordering */
import { validator } from '@liskhq/lisk-validator';
import {
BaseModule,
ModuleInitArgs,
ModuleMetadata,
// InsertAssetContext,
// BlockVerifyContext,
TransactionVerifyContext,
VerificationResult,
// TransactionExecuteContext,
// GenesisBlockExecuteContext,
// BlockExecuteContext,
// BlockAfterExecuteContext,
VerifyStatus,
utils,
} from 'lisk-sdk';
import { CreateHelloCommand } from './commands/create_hello_command';
import { HelloEndpoint } from './endpoint';
import { HelloMethod } from './method';
import { configSchema, getHelloRequestSchema, getHelloResponseSchema } from './schema';
import { MessageStore } from './stores/message';
import { ModuleConfigJSON } from './types';
export const defaultConfig = {
maxMessageLength: 256,
minMessageLength: 3,
blacklist: ['illegalWord1'],
};
export class HelloModule extends BaseModule {
public endpoint = new HelloEndpoint(this.stores, this.offchainStores);
public method = new HelloMethod(this.stores, this.events);
public commands = [new CreateHelloCommand(this.stores, this.events)];
public constructor() {
super();
// registeration of stores and events
this.stores.register(MessageStore, new MessageStore(this.name, 0));
}
public metadata(): ModuleMetadata {
return {
endpoints: [
{
name: this.endpoint.getHello.name,
request: getHelloRequestSchema,
response: getHelloResponseSchema,
},
],
commands: this.commands.map(command => ({
name: command.name,
params: command.schema,
})),
events: this.events.values().map(v => ({
name: v.name,
data: v.schema,
})),
assets: [],
stores: [],
};
}
// Lifecycle hooks
public async init(args: ModuleInitArgs): Promise<void> {
// Get the module config defined in the config.json of the node
const { moduleConfig } = args;
// Overwrite the default module config with values from config.json, if set
const config = utils.objects.mergeDeep({}, defaultConfig, moduleConfig) as ModuleConfigJSON;
// Validate the config with the config schema
validator.validate<ModuleConfigJSON>(configSchema, config);
// Call the command init() method with config as parameter
this.commands[0].init(config).catch(err => {
console.log('Error: ', err);
});
}
// public async insertAssets(_context: InsertAssetContext) {
// // initialize block generation, add asset
// }
// public async verifyAssets(_context: BlockVerifyContext): Promise<void> {
// // verify block
// }
// Lifecycle hooks
public async verifyTransaction(context: TransactionVerifyContext): Promise<VerificationResult> {
// verify transaction will be called multiple times in the transaction pool
context.logger.info('TX VERIFICATION');
return { status: VerifyStatus.OK };
}
// public async beforeCommandExecute(_context: TransactionExecuteContext): Promise<void> {
// }
// public async afterCommandExecute(_context: TransactionExecuteContext): Promise<void> {
// }
// public async initGenesisState(_context: GenesisBlockExecuteContext): Promise<void> {
// }
// public async finalizeGenesisState(_context: GenesisBlockExecuteContext): Promise<void> {
// }
// public async beforeTransactionsExecute(_context: BlockExecuteContext): Promise<void> {
// }
// public async afterTransactionsExecute(_context: BlockAfterExecuteContext): Promise<void> {
// }
}