Dappsの運用方法を考える
レイヤー2ソリューションやinteroperabilityが重要視されており、実ビジネスでの利用が徐々に考えられています。
まだ整理中なので誤っているかもしれないのですが、Dappsの運用方法を整理してみました。
運用方法 | Public以外で利用するツール |
---|---|
オフチェーンで運用して、Publicに重要な時だけ書き込む(取引所型) | RDBなど |
パーミッションドチェーン(Private / Consortium)で運用して、PublicとPrivateのInteroperabilityを確立する | private chain + / parity-bridge, Cosmos(Tendermint)、Polkadot |
PrivateとPublicの間にトラストできる仕組みを設ける | private chain + 透明性のある仕組み・開示されている仕組みなどをオフチェーンで作る |
パーミッションレスなサイドチェーンで運用して、たまにPublicに書き込む | Plasma / Cross Chain |
この分類で完全にダブりがないわけではないと思いますが(特に4番目のものとか)、一旦自身の整理として書きました。
オフチェーン+Public Chain以外の場合、パーミッションドチェーン(Private / Consortium)であったり、サイドチェーンであったりを運用することになります。となると、それぞれのチェーンの用意はもちろん必要です。パーミッションドチェーン(Private / Consortium)でも、書き込む権限やブロードキャストできる権限はノード参加者に限るとしても読み込み権限は多くに解放する、エンジニアでなくてもわかりやすいようにデータを確認できるよう、エクスプローラーを立てるといったこともありえそうです。
パーミッションドチェーンとの両立をしたInteroperabilityが現時点では割と面白いのではないかと思っており、Cosmos-SDKを使ってみます。
Cosmos-SDKとは
The Cosmos-SDK is a framework for building multi-asset Proof-of-Stake (PoS) blockchains, like the Cosmos Hub, as well as Proof-Of-Authority (PoA) blockchains.
Cosmos-SDKは、Cosmos-Hubのような複数通貨のPoS / PoAブロックチェーンを作るためのフレームワークです。
Cosmos-HubはZoneと呼ばれるチェーンと、Cosmos-HubをIBCというプロトコルで通信することで各Zoneのトークンの合計数を記録できます
https://cosmos.network/docs/intro/
Cosmosはアーキテクチャとして、ConsensusとNetworkingはTendermint Coreで作成しており、アプリケーションレイヤーをCosmos-SDKで利用しています。
^ +-------------------------------+ ^ | | | | Built with Cosmos SDK | | State-machine = Application | | | | | v | +-------------------------------+ | | | ^ Blockchain node | | Consensus | | | | | | | +-------------------------------+ | Tendermint Core | | | | | | Networking | | | | | | v +-------------------------------+ v
ABCI(Application Block Chain Interface)というプロトコル(Tendermintの基盤プロトコル)でアプリケーションレイヤーからTendermintに通信をして情報を取得します。
+---------------------+ | | | Application | | | +--------+---+--------+ ^ | | | ABCI | v +--------+---+--------+ | | | | | Tendermint | | | | | +---------------------+
スマートコントラクトについて
Cosmos-SDKではEthereum形式のスマートコントラクトはサポートしておらず、go言語で独自ブロックチェーンを作るという方向性の設計になっています。後述しますが、Msgと呼ばれる形式の構造体によって任意の情報を定義したり、受け取った時のアクションを設定したりできます。
Cosmos-SDKチュートリアルアプリの実装概要
では早速チュートリアルを実行しながら内容を理解してみます。
チュートリアルアプリnameserviceは名前(ドメインネームなどを想定していそう)を管理したり売買したりするようなプラットフォームです。チュートリアルでは以下のプログラムを実装する作りになっています。
- app.go
- handler.go
- keeper.go
- msgs.go
- querier.go
- codec.go
app(baseapp)
cosmos-sdkはbaseappと呼ばれる基本フレームワークをもとに動いています。 "./app.go"を作って必要なロジックを書いていきます。チュートリアルでは、nameServiceAppという構造体を作って、さらに BaseAppを見るようにしています。
bam "github.com/cosmos/cosmos-sdk/baseapp" type nameServiceApp struct { *bam.BaseApp
BaseAppはtendermintのABCIを実装しており、自力でABCIプロトコルを書く必要はありません。
Keeper
KeeperはCosmos-SDKのbank package内にあります。コインの足し引きなどを計算する番人としてのmodelです。チュートリアルのプログラムを以下などを見ると、Name(名前)、owner(所有者)、price(値段)を管理するモデルを書いてラップしています。
// Keeper maintains the link to data storage and exposes getter/setter methods for the various parts of the state machine type Keeper struct { coinKeeper bank.Keeper namesStoreKey sdk.StoreKey // Unexposed key to access name store from sdk.Context ownersStoreKey sdk.StoreKey // Unexposed key to access owners store from sdk.Context pricesStoreKey sdk.StoreKey // Unexposed key to access prices store from sdk.Context cdc *codec.Codec // The wire codec for binary encoding/decoding. } // ResolveName - returns the string that the name resolves to func (k Keeper) ResolveName(ctx sdk.Context, name string) string { store := ctx.KVStore(k.namesStoreKey) bz := store.Get([]byte(name)) return string(bz) } // SetName - sets the value string that a name resolves to func (k Keeper) SetName(ctx sdk.Context, name string, value string) { store := ctx.KVStore(k.namesStoreKey) store.Set([]byte(name), []byte(value)) } // HasOwner - returns whether or not the name already has an owner func (k Keeper) HasOwner(ctx sdk.Context, name string) bool { store := ctx.KVStore(k.ownersStoreKey) bz := store.Get([]byte(name)) return bz != nil } // GetOwner - get the current owner of a name func (k Keeper) GetOwner(ctx sdk.Context, name string) sdk.AccAddress { store := ctx.KVStore(k.ownersStoreKey) bz := store.Get([]byte(name)) return bz }
Msg
Msgは送信されるTxにWrapされるもので、Cosmos-SDKを利用していると、DeveloperはTxの細かい設定は気にせずMsg開発にだけ集中できるとのことです。
チュートリアルで書かれているMsgの定義例は以下などです。Nameについて項目を設定しています。
// MsgSetName defines a SetName message type MsgSetName struct { Name string Value string Owner sdk.AccAddress }
Handler
HandlerはMsgを受け取った時にどのようにするかという対応です。 以下はMsgSetNameを受けとった時のロジックです。
// Handle MsgSetName func handleMsgSetName(ctx sdk.Context, keeper Keeper, msg MsgSetName) sdk.Result { if !msg.Owner.Equals(keeper.GetOwner(ctx, msg.Name)) { // Checks if the the msg sender is the same as the current owner return sdk.ErrUnauthorized("Incorrect Owner").Result() // If not, throw an error } keeper.SetName(ctx, msg.Name, msg.Value) // If so, set the name to the value specified in the msg. return sdk.Result{} // return }
Querier
クエリ処理をするロジックです。queryResolve、QueryWhoisの2つを用意しており、名前解決するクエリと所有者を返すクエリを持っています。これらは以下のようにswitchして実行funcを定義します。
// NewQuerier is the module level router for state queries func NewQuerier(keeper Keeper) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { switch path[0] { case QueryResolve: return queryResolve(ctx, path[1:], req, keeper) case QueryWhois: return queryWhois(ctx, path[1:], req, keeper) default: return nil, sdk.ErrUnknownRequest("unknown nameservice query endpoint") } } }
後ほどでてきますが、以下などが使用例です。
$ nscli query nameservice resolve jack.id --chain-id testchain
codec
codecは独自のmodelを設定したら登録する必要があります。
// RegisterCodec registers concrete types on the Amino codec func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgSetName{}, "nameservice/SetName", nil) cdc.RegisterConcrete(MsgBuyName{}, "nameservice/BuyName", nil) }
Cosmos-SDKチュートリアルアプリの実行
チュートリアルアプリを実行してみます。Cosmosはgoで書かれています。 go versionはgo1.11です。
$ cd ~/$GOPATH/src/github.com/ $ git clone https://github.com/cosmos/sdk-application-tutorial.git
# export go111MODULE=OFF #GO111MODULEをoffにしておかないとうまく動きません
$ make get_tools && make get_vendor_deps && make install
- make get_toolsではdepというgolangの依存関係管理ツールのインストール
- make get_vendor_depsでは依存関係のあるツールをインストール
- make installではnsdとnscliをインストール
します。ちなみに、nscliとnsdは、cobraというコマンドライブラリでコマンドを作成しています。
nsはnamespaceの略で、namespacecliとnamespacedeamonのことを指しています。
nscliのコマンド
$ nscli help nameservice Client Usage: nscli [command] Available Commands: status Query remote node for status config Create or query a Gaia CLI configuration file query Querying subcommands tx Transactions subcommands rest-server Start LCD (light-client daemon), a local REST server keys Add or view local private keys version Print the app version help Help about any command Flags: --chain-id string Chain ID of tendermint node -e, --encoding string Binary encoding (hex|b64|btc) (default "hex") -h, --help help for nscli --home string directory for config and data (default "/Users/nao/.nscli") -o, --output string Output format (text|json) (default "text") --trace print out full stack trace on errors Use "nscli [command] --help" for more information about a command.
nsdのコマンド
$ nsd help nameservice App Daemon (server) Usage: nsd [command] Available Commands: init Initialize genesis config, priv-validator file, and p2p-node file add-genesis-account Adds an account to the genesis file start Run the full node unsafe-reset-all Resets the blockchain database, removes address book files, and resets priv_validator.json to the genesis state tendermint Tendermint subcommands export Export state to JSON version Print the app version help Help about any command Flags: -h, --help help for nsd --home string directory for config and data (default "/Users/nao/.nsd") --log_level string Log level (default "main:info,state:info,*:error") --trace print out full stack trace on errors Use "nsd [command] --help" for more information about a command.
なお、nsdからtendermintのサブコマンドも呼べて、以下のようになります。
$ nsd tendermint help Tendermint subcommands Usage: nsd tendermint [command] Available Commands: show-node-id Show this node's ID show-validator Show this node's tendermint validator info show-address Shows this node's tendermint validator consensus address version Print tendermint libraries' version Flags: -h, --help help for tendermint Global Flags: --home string directory for config and data (default "/Users/nao/.nsd") --log_level string Log level (default "main:info,state:info,*:error") --trace print out full stack trace on errors Use "nsd tendermint [command] --help" for more information about a command.
testchainのセットアップ
test chain 初期化
$ nsd init --chain-id testchain Initialized nsd configuration and bootstrapping files in /Users/nao/.nsd...
jack とaliceのkeyを作成
$ nscli keys add jack Enter a passphrase to encrypt your key to disk: Repeat the passphrase: NAME: TYPE: ADDRESS: PUBKEY: jack local cosmos1r70ad4fmkcsfs6r5wzll7rlnft4edpn3j6asyk cosmospub1addwnpepqgf3ejr92547hm2gzdh7a9u0p52v87cyjcp6jualcsf8a7j5jdjlwu5nuj4 **Important** write this seed phrase in a safe place. It is the only way to recover your account if you ever forget your password. abandon fragile coin better wrestle later walnut talent control naive fashion toddler tooth clean drift intact victory cupboard drip fantasy keen deer magic text $ nscli keys add alice Enter a passphrase to encrypt your key to disk: Repeat the passphrase: NAME: TYPE: ADDRESS: PUBKEY: alice local cosmos1npzzr724p57sxjzmtvppgru987zu6qewkxv7pt cosmospub1addwnpepqw2gd6jjxguk97xzgls2666crfr8kfewnxk8jg69wctsd94vadhnqmj8c3j **Important** write this seed phrase in a safe place. It is the only way to recover your account if you ever forget your password. popular lazy scheme suit black property slight wet waste light then minor flavor liquid lobster weather fun height sure category blast furnace alter cost
jackとaliceの初期アカウントを作成する
# 公式通りにやると大文字が使えないと怒られるので、小文字にする error message (ERROR: denom cannot contain upper case characters: jackCoin $ nsd add-genesis-account $(nscli keys show jack --address) 1000mycoin,1000jackcoin $ nsd add-genesis-account $(nscli keys show alice --address) 1000mycoin,1000alicecoin
フルノード起動
$ nsd start I[2019-02-05|01:12:05.554] Starting ABCI with Tendermint module=main E[2019-02-05|01:12:05.690] Couldn't connect to any seeds module=p2p I[2019-02-05|01:12:10.657] Executed block module=state height=0 validTxs=0 invalidTxs=0 I[2019-02-05|01:12:10.671] Committed state module=state height=0 txs=0 appHash=6233E01F84FEEC8F11D24EE17E50E4EBB6B3E32969060A136A741ADF41F2E307
jackの残高確認
$ nscli query account $(nscli keys show jack --address) \ > --indent --chain-id testchain Account: Address: cosmos1r70ad4fmkcsfs6r5wzll7rlnft4edpn3j6asyk Pubkey: Coins: 1000jackcoin,1000mycoin AccountNumber: 0 Sequence: 0
jackが独自ID(jack.id)というドメイン名を購入
トランザクションを作ります。jackのパスワードを求められます。
$ nscli tx nameservice buy-name jack.id 5mycoin \ > --from $(nscli keys show jack --address) \ > --chain-id testchain Password to sign with 'jack': Committed at block 28 (tx hash: 42177F9986922F4FD7230A1BBBFA59FE0BD6B45ABE37FAC01200E190DFB1C933, response: {Code:0 Data:[] Log:Msg 0: Info: GasWanted:200000 GasUsed:21872 Tags:[{Key:[97 99 116 105 111 110] Value:[98 117 121 95 110 97 109 101] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0}] Codespace: XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0})
ノードログを見ると、トランザクションが発行されています。
$ nsd start ... I[2019-02-05|02:08:43.495] Executed block module=state height=28 validTxs=1 invalidTxs=0 I[2019-02-05|02:08:43.496] Committed state module=state height=28 txs=1 appHash=C559CAF524A6FAA38F324327FECB18888149E9B497F34D58CEF96B4079A0ED47
jackの残高を見ると、残高が減っています。
$ nscli query account $(nscli keys show jack --address) \ > --indent --chain-id testchain Account: Address: cosmos1r70ad4fmkcsfs6r5wzll7rlnft4edpn3j6asyk Pubkey: cosmospub1addwnpepqgf3ejr92547hm2gzdh7a9u0p52v87cyjcp6jualcsf8a7j5jdjlwu5nuj4 Coins: 1000jackcoin,995mycoin AccountNumber: 0 Sequence: 1
jack.idに8.8.8.8という値をつけてみます。
# idに値を設定 $ nscli tx nameservice set-name jack.id 8.8.8.8 \ > --from $(nscli keys show jack --address) \ > --chain-id testchain Password to sign with 'jack': Committed at block 161 (tx hash: 8A8C8B5B63AA473BB4BB5D4332B9EBAB9BEAF84E8E3BA121AF7A3D87C50D4F84, response: {Code:0 Data:[] Log:Msg 0: Info: GasWanted:200000 GasUsed:10603 Tags:[{Key:[97 99 116 105 111 110] Value:[115 101 116 95 110 97 109 101] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0}] Codespace: XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0}) # 確認 $ nscli query nameservice resolve jack.id --chain-id testchain 8.8.8.8
resolveコマンドは上で設定していたコマンドですね。
jackがかったドメインの所有権を確認する
$ nscli query nameservice whois jack.id --chain-id testchain { "value": "8.8.8.8", "owner": "cosmos1r70ad4fmkcsfs6r5wzll7rlnft4edpn3j6asyk", # jackのアドレス "price": [ { "denom": "mycoin", "amount": "5" } ] }
aliceがjackからドメインjack.idを購入する
$ nscli tx nameservice buy-name jack.id 10mycoin \ > --from $(nscli keys show alice --address) \ > --chain-id testchain Password to sign with 'alice': Committed at block 175 (tx hash: D4A26EDFBCB094ABA83EE982E20BD29F270DC1B67A7DEADA20F6556FCAA98338, response: {Code:0 Data:[] Log:Msg 0: Info: GasWanted:200000 GasUsed:31994 Tags:[{Key:[97 99 116 105 111 110] Value:[98 117 121 95 110 97 109 101] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0}] Codespace: XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0})
whoisコマンドで見ると、jackからaliceに所有権が移転しています。
$ nscli query nameservice whois jack.id --chain-id testchain { "value": "8.8.8.8", "owner": "cosmos1npzzr724p57sxjzmtvppgru987zu6qewkxv7pt", # aliceのアドレス "price": [ { "denom": "mycoin", "amount": "10" } ] }
感想
ビジネスロジックをチェーンに追加して開発するという事はしやすい気がしました。あとは、Cosmosでのチェーンでパーミッション機能設定がどうあるかなどはまだ調べていないので、引き続き追っていきたいと思います。
また、cosmosにはオープンソースのexplorerもあるのですが、ローカルのデータとうまくまだ繋げられていないので別途こちらもみてみたいと思います。