Cosmos-SDK(1)
目次
1. Cosmos SDK + 各コンポーネントの概略
2. ハンズオン (簡単なアプリケーションの作成
3. moonty_sal.iconがbuidlxで作成しているものの紹介
TL; DR
tendermintをコンセンサスのレイヤーに用いてアプリケーション固有のブロックチェーンを作成するためのSDK
PoSをコンセンサスアルゴリズムに採用したアプリケーション固有のチェーンを構築できる
Networking / Consensusのレイヤを意識せずに開発者はアプリケーション固有のロジックだけ記述すればい
CosmosSDKを利用するためのローカルのチェーン(Ganache的なやつ)の名前がgaia
brew install gaia する(パワーを感じる)
複雑なロジックを実装するのは骨が折れそうだがPoS+ビジネスロジックのアプリケーションを作りたい場合は重宝しそう。コンポーネント化されており、情報の流れもわかりやすい。
他のアプリケーション(固有のブロックチェーン)ともCosmosSDK経由で相互通信が可能
https://gyazo.com/5f1950a8951376e456f121f5eff644f1
CosmosSDKで扱うのはアプリケーション固有のロジックの部分だけ
https://gyazo.com/5d7cb8175b04b30d98ad377479b7d62f
ところで、「アプリケーション固有のロジック」とは何なのだろう?
ここでは、Cosmos-SDKのチュートリアルにあるNameServiceの実装例を見ながら、アプリケーション固有のロジックとその実装方法について見ていく
基本的な考え方
Tx: [ Msg, Msg, Msg .... ]トランザクションはメッセージの集まり
messageによってブロックチェーンの(各アカウントの)状態が変化する
残高とか、誰が何を持ってるとか(ゲームだったら敵のHPとか)
NameServiceのロジック
nameに対してvalueを割り当てることが出来る
DNSの場合は name に対してIPを対応させる。]
BuyerはOwnerのいないNameをcoinで購入し、値をセットすることができる = SetName
BuyerはOwnerのいるNameをOwnerから購入することができる = BuyName
アプリケーションの構造
code:dir.sh
./nameservice
├── Gopkg.toml
├── Makefile
├── app.go # エントリポイント
├── cmd
│ ├── nscli
│ │ └── main.go # name service cliの略でnscli
│ └── nsd
│ └── main.go
└── x
└── nameservice
├── client
│ └── cli
│ ├── query.go # COSMOS SDKで実装したアプリケーションに対して投げるクエリを構築する部分
│ └── tx.go # トランザクションを発行する君
├── codec.go # メッセージのデータ構造を規定する部分
├── handler.go # リクエストを定説な
├── keeper.go # データベースとしてblockchainを見た時に、そのデータベースへのアクセスを担う君
├── msgs.go # ブロックチェーン上のアプリケーションでやり取りされるメッセージ
└── querier.go # データベースへクエリを投げる君(クエリビルダーっぽい部分)
Module構造
Keeper
データベースへのアクセスを代行する。各エンティティ(ビジネスロジックが扱っているデータモデル)ごとKeeperが存在する
NameServiceの場合
NameKeeperが下記のKeyValue Storeを持つ
Name -> Value
Name -> Owner
Name -> Price
ネイティブのCoinを扱うKeeperとしてBankKeeperというのが用意されている
bank.Keeper 的な型が定義されている
データストアに対するアクセス制御もよしなに行う
Capability-based Securityとおいうセキュリティモデルが採用されている
Msg(s)
メッセージは型や、誰が署名しているといった情報を取得する関数や、簡単なValidate関数が定義されている必要がある
code:message_interface.go
// Transactions messages must fulfill the Msg
type Msg interface {
// Return the message type.
// Must be alphanumeric or empty.
Type() string
// Returns a human-readable string for the message, intended for utilization
// within tags
Route() string
// ValidateBasic does a simple validation check that
// doesn't require access to any other information.
ValidateBasic() Error
// Get the canonical byte representation of the Msg.
GetSignBytes() []byte
// Signers returns the addrs of signers that must sign.
// CONTRACT: All signatures must be present to be valid.
// CONTRACT: Returns addrs in some deterministic order.
GetSigners() []AccAddress
}
Handler
ウェブアプリでいうRouter,飛んできたMsgの種類を見て適切な処理を実行する
code: handler.go
package nameservice
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// NewHandler returns a handler for "nameservice" type messages.
func NewHandler(keeper Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case MsgSetName:
return handleMsgSetName(ctx, keeper, msg)
default:
errMsg := fmt.Sprintf("Unrecognized nameservice Msg type: %v", msg.Type())
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}
Querier
ブロックチェーンの状態を参照してJSON等の形式で返却する君
Codec
TendermintはAminoというフォーマットでデータを保存しているのでAnimoとしてデータの形式を登録したりする君
AminoはProtocol Buffler3のsubset
データシリアライズフォーマット
Amino
Bring parity into logic objects and persistent objects by supporting interfaces.
Have a unique/deterministic encoding of value.
Binary bytes must be decodeable with a schema.
Schema must be upgradeable.
Sufficient structure must be parseable without a schema.
The encoder and decoder logic must be reasonably simple.
The serialization must be reasonably compact.
A sufficiently compatible JSON format must be maintained (but not general conversion to/from JSON)
JSONでもいいけどもっと効率的に出来るよねというお話だそう
Interfaces
REST
handlerの関数に対してRESTのエンドポイントを手軽に作成できる
帰ってきた値をHTTPの言葉に変換
StatusCodeとか
responseの形とか
code:rest_tx.go
func buyNameHandler(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req buyNameReq
err := utils.ReadRESTReq(w, r, cdc, &req)
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
baseReq := req.BaseReq.Sanitize()
if !baseReq.ValidateBasic(w) {
return
}
Client(CLI)
CLI作るのにcobraというCLI作成用のライブラリを使ってるのでそれに準拠してCLIからいろいろなトランザクションを発行する
Query
Transaction
基本的なデータ構造
一覧
https://gyazo.com/55c36cbc67f138610b9a8ef437ce6dde
トランザクション
https://gyazo.com/575b300e1be1b8da4f6cc7153270cc14
AntiHandler(メッセージが提出されて初めに働くハンドラ
アンティは強制ベットの一形式で、カードが配られる前にテーブルにいるすべてのプレイヤーが支払う必要のあるものです。それは、テーブルのすべてのプレイヤーがラウンド毎に支払うもので、ベットとはみなされず、ラウンドへの一種の参加料とみなされます。
https://gyazo.com/39c03fc32112a0dd0aa653fe54845338
MsgとかKeeperは上述のとおり
各モジュールの動作図
https://gyazo.com/0ffa421d21814052ac4329e315b1ae88
Msgの所で必要な署名の検証などハンドリングできる
code: setname.go
// ValdateBasic Implements Msg.
func (msg MsgSetName) ValidateBasic() sdk.Error {
if msg.Owner.Empty() {
return sdk.ErrInvalidAddress(msg.Owner.String())
}
if len(msg.NameID) == 0 || len(msg.Value) == 0 {
return sdk.ErrUnknownRequest("Name and/or Value cannot be empty")
}
return nil
}
code:buyname.go
func (msg MsgBuyName) ValidateBasic() sdk.Error {
if msg.Buyer.Empty() {
return sdk.ErrInvalidAddress(msg.Buyer.String())
}
if len(msg.NameID) == 0 {
return sdk.ErrUnknownRequest("Name cannot be empty")
}
if !msg.Bid.IsPositive() {
return sdk.ErrInsufficientCoins("Bids must be positive")
}
return nil
}
サンプルプログラムの動作例
アカウント作成
code: sample.sh
# Initialize configuration files and genesis file
nsd init --chain-id testchain
# Copy the Address output here and save it for later use
nscli keys add salva
# Copy the Address output here and save it for later use
nscli keys add anastasia
# Add both accounts, with coins to the genesis file
nsd add-genesis-account $(nscli keys show salva --address) 1000mycoin,1000salvaCoin
nsd add-genesis-account $(nscli keys show anastasia --address) 1000mycoin,1000anastasiaCoin
NameServiceの利用
code:use_nameservice.sh
# First check the accounts to ensure they have funds
nscli query account $(nscli keys show salva --address) \
--indent --chain-id testchain
nscli query account $(nscli keys show anastasia --address) \
--indent --chain-id testchain
# Buy your first name using your coins from the genesis file
nscli tx nameservice buy-name salva.id 5mycoin \
--from $(nscli keys show salva --address) \
--chain-id testchain
# Set the value for the name you just bought
nscli tx nameservice set-name salva.id 8.8.8.8 \
--from $(nscli keys show salva --address) \
--chain-id testchain
# Try out a resolve query against the name you registered
nscli query nameservice resolve salva.id --chain-id testchain
# > 8.8.8.8
# Try out a whois query against the name you just registered
nscli query nameservice whois salva.id --chain-id testchain
# Alice buys name from jack
nscli tx nameservice buy-name salva.id 10mycoin \
--from $(nscli keys show anastasia --address) \
--chain-id testchain