はじめに
Liskって名前を知っていてもLisk SDKに触れたことがある人は少ないのでは、ということで今回記事にしました。
丁度いいところに、アドベントカレンダーもあったしね。
構築手順を書いているので少し長いですがお付き合いください。
Lisk SDK ってなんぞ
ブロックチェーンとその上で動作するアプリケーション(ブロックチェーンアプリケーション)を構築するための開発キット。
これを使用して作成されたアプリケーションは独自のブロックチェーン上で動作します。
また、将来的にサイドチェーンとしてLiskにつながることが可能になります。
なお、JavaScript、TypeScriptで構築できるので非常に楽。(いや、ホントに)
サイドチェーンとしてLiskと繋ぐ部分(インターオペラビリティ周り)はまだ開発中
サイドチェーンとなる必要がないなら、今のままでも新プロジェクトとして立ち上げられるくらいの実力有り
推奨構築環境
OS
-
ubuntu
18.04 (LTS)
20.04 (LTS) -
mac
10.13 (High Sierra)
10.14 (Mojave)
10.15 (Catalina)
11.04 (Big Sur)
Windowsは残念ながら...
RAM
2GB以上
Node.js
v12.22.7 (NPM version 6.14.15)
v12系以外を使用するとうまく動作しません
構築方法
1. 下準備
とりあえずこの辺いれとく
sudo apt-get install curl wget tar unzip zip ntp jq git build-essential
2. Lisk Commander の導入
npm i -g lisk-commander
とても便利なCLI
入れなくても構築できるが、入れておいて損はなし
今回の記事では入れている前提
3. ブロックチェーンアプリケーションのプロジェクトを作成
lisk init omikuji
lisk init [アプリケーション名]
4. 作成したプロジェクトを開く
以降 Visual Studio Codeなどを使って作業するのをおすすめ
5. 便利なプラグインの追加
以下のファイルを編集
import { Application, HTTPAPIPlugin } from 'lisk-sdk'; // add HTTPAPIPlugin
import { DashboardPlugin } from '@liskhq/lisk-framework-dashboard-plugin'; // add DashboardPlugin
// @ts-expect-error Unused variable error happens here until at least one module is registered
export const registerPlugins = (app: Application): void => {
app.registerPlugin(HTTPAPIPlugin); // register HTTPAPIPlugin
app.registerPlugin(DashboardPlugin); // register DashboardPlugin
};
HTTPAPIPlugin:ブラウザ等でAPIをたたきたいときに必須
DashboardPlugin:構築したアプリケーションの動作確認に使用できるので開発中は入れておくと便利
6. モジュールとアセットの作成
Lisk SDKではモジュールとアセットで1つのトランザクションとして扱われます。
Lisk標準のモジュール・アセット(一部抜粋)
moduleID | assetID | name |
---|---|---|
2 | 0 | token:transfer |
5 | 0 | dpos:register |
5 | 1 | dpos:vote |
5 | 2 | dpos:unlock |
今回はおみくじを作りたいので、以下のような感じにします。
moduleID | assetID | name |
---|---|---|
3535 | 0 | omikuji:pull |
ということで
lisk generate:module omikuji 3535
lisk generate:asset omikuji pull 0
lisk generate:module [モジュール名] [モジュールID]
lisk generate:asset [モジュール名] [アセット名] [アセットID]
ここまでやると以下のファイルが作成されます。
/* eslint-disable class-methods-use-this */
import {
AfterBlockApplyContext,
AfterGenesisBlockApplyContext, BaseModule,
BeforeBlockApplyContext, TransactionApplyContext
} from 'lisk-sdk';
import { PullAsset } from "./assets/pull_asset";
export class OmikujiModule extends BaseModule {
......
public name = 'omikuji';
public transactionAssets = [new PullAsset()];
public events = [
// Example below
// 'omikuji:newBlock',
];
public id = 3535;
......
public async afterGenesisBlockApply(_input: AfterGenesisBlockApplyContext) {
// Get any data from genesis block, for example get all genesis accounts
// const genesisAccounts = genesisBlock.header.asset.accounts;
}
}
import { BaseAsset, ApplyAssetContext, ValidateAssetContext } from 'lisk-sdk';
export class PullAsset extends BaseAsset {
public name = 'pull';
public id = 0;
// Define schema for asset
public schema = {
$id: 'omikuji/pull-asset',
title: 'PullAsset transaction asset for omikuji module',
type: 'object',
required: [],
properties: {},
};
public validate({ asset }: ValidateAssetContext<{}>): void {
// Validate your asset
}
// eslint-disable-next-line @typescript-eslint/require-await
public async apply({ asset, transaction, stateStore }: ApplyAssetContext<{}>): Promise<void> {
throw new Error('Asset "pull" apply hook is not implemented.');
}
}
また、以下のファイルに作成されたモジュールが自動で追加されます。
import { Application } from 'lisk-sdk';
import { OmikujiModule } from "./modules/omikuji/omikuji_module"; // add
// @ts-expect-error Unused variable error happens here until at least one module is registered
export const registerModules = (app: Application): void => {
app.registerModule(OmikujiModule); // add
};
7. モジュールとアセットの編集
あとは自由にどんなアプリケーションにしたいか考えながら作成されたファイルを編集していきます。
今回のおみくじは以下のような仕様
- 1LSK以上つかってお願いすると「大吉、吉、中吉、小吉、末吉、凶、大凶」を教えてくれる
- 1LSK未満だと神様に怒られる
- おみくじの結果はアカウント情報のomikuji.kekkaに設定される
- おみくじを引く際のパラメータは {"onegai": 数値}
/* eslint-disable class-methods-use-this */
import {
AfterBlockApplyContext,
AfterGenesisBlockApplyContext, BaseModule,
BeforeBlockApplyContext, TransactionApplyContext
} from 'lisk-sdk';
import { PullAsset } from "./assets/pull_asset";
export class OmikujiModule extends BaseModule {
public actions = {};
public reducers = {};
public name = 'omikuji';
public transactionAssets = [new PullAsset()];
public events = [];
public id = 3535;
// アカウント情報に追加するおみくじの結果を設定するためのスキーマの設定
public accountSchema = {
type: 'object',
properties: {
kekka: {
fieldNumber: 1,
dataType: 'string',
maxLength: 64,
},
},
default: {
kekka: '',
},
};
// Lifecycle hooks
public async beforeBlockApply(_input: BeforeBlockApplyContext) {}
public async afterBlockApply(_input: AfterBlockApplyContext) {}
public async beforeTransactionApply(_input: TransactionApplyContext) {}
public async afterTransactionApply(_input: TransactionApplyContext) {}
public async afterGenesisBlockApply(_input: AfterGenesisBlockApplyContext) {}
}
import { BaseAsset, ApplyAssetContext, ValidateAssetContext, cryptography } from 'lisk-sdk';
export class PullAsset extends BaseAsset {
public name = 'pull';
public id = 0;
// トランザクション実行時のパラメータ用のスキーマを設定
public schema = {
$id: 'omikuji/pull-asset',
title: 'PullAsset transaction asset for omikuji module',
type: 'object',
required: ["onegai"],
properties: {
onegai: {
dataType: 'uint64',
fieldNumber: 1,
},
},
};
public validate({ asset }: ValidateAssetContext<{onegai:BigInt}>): void {
// 1LSK未満なら怒る
if (asset.onegai < BigInt(100000000)) {
throw new Error(
'1LSKくらいはらうのじゃー'
);
}
}
// eslint-disable-next-line @typescript-eslint/require-await
public async apply({ asset, transaction, reducerHandler, stateStore }: ApplyAssetContext<{onegai:BigInt}>): Promise<void> {
// おみくじ
const kuji = ["大吉","吉","中吉","小吉", "末吉", "凶", "大凶"];
const kekka = kuji[parseInt(cryptography.bufferToHex(transaction.id).slice(0, 8), 16) % kuji.length];
// 送信者の情報に結果を設定
const senderAddress = transaction.senderAddress;
const senderAccount:{address: Buffer, omikuji:{kekka:string}} = await stateStore.account.get(senderAddress);
senderAccount.omikuji.kekka = kekka;
stateStore.account.set(senderAccount.address, senderAccount);
// 送信者の残高からお願いに使ったLSKを減らす
await reducerHandler.invoke("token:debit", {
address: senderAddress,
amount: asset.onegai,
});
}
}
8. Genesis Blockの作成準備
./bin/run genesis-block:create --output config/default
A genesis_block file already exists at the given location. Do you want to overwrite it?と聞かれたらy
すると、config/defaultに以下のファイルが生成されます
- accounts.json
- forging_info.json
- genesis_block.json
- password.json
config/default内の各JSONファイル(config.jsonを含む)はGitHubなどで公開する際は十分気をつけましょう。
ここで生成されるファイルを公開し、そのままアプリをリリースしようものなら大変なことになります。
絶対にアカウントのパスフレーズや、パスワードは公開してはいけません。
forging_info.jsonおよびpassword.jsonの内容をconfig.jsonに反映
tmp=$(mktemp)
jq '.forging.delegates = input' config/default/config.json config/default/forging_info.json > "$tmp" && mv "$tmp" config/default/config.json
jq '.forging += input' config/default/config.json config/default/password.json > "$tmp" && mv "$tmp" config/default/config.json
9. ブロックチェーンアプリケーションの起動
./bin/run start --api-ws
10. 動作確認
ブラウザから http://localhost:4005 にアクセス
Send transactionから作成したモジュール・アセットを選択して「Submit」
動作確認の際は accounts.json に記載されているものを使用すると楽
なお、1LSK = 100,000,000
HttpAPIで成功した際のトランザクションIDを確認してみる
HttpAPIでおみくじの結果(送信者のアカウント情報)を確認してみる
「大凶」だったみたいです...
ということで
SDKの機能の1割も使ってないんじゃないかというくらい簡単なアプリでしたがなんとなくイメージは掴めたでしょうか?
必要スペックも低い、ブロックチェーン構築も楽、アプリケーションの実装も楽
言うことなしだと思いません!?
ぜひ触ってみてくださいね!
なお、開発で困ったことがあれば Lisk 公式Discord や、Lisk Dev forum で尋ねるとコミュニティの誰かがきっと助けてくれます!
また、ブロックチェーンアプリケーションを本格的なプロジェクトとして構築していくのであればGrant Programに応募してみてはどうでしょうか?
読んでいただきありがとうございました!
お疲れ様でした!
補足:ブロックチェーンアプリケーションの削除
rm -rf ~/.lisk/[アプリケーション名]
データのみ消す場合は
rm -rf ~/.lisk/[アプリケーション名]/data