protobufによるフレキシブルなコントラクトデータの管理

### 今日はdappの運用にprotobufを使うこと、のお話なんですがその前に

---
### 動機：ゲーム向けのサーバーフレームワークとしてのEthereum

---
### 前提
- コントラクトのコードはimmutable

---
### 前提
- コントラクトのコードはimmutable
- コントラクトのデータ構造はコードでしか定義できない

---
### 前提
- コントラクトのコードはimmutable
- コントラクトのデータ構造はコードでしか定義できない 

 => コントラクトのデータ構造はimmutable

---
### 懸念：最初のコントラクトに無いデータが必要になったらどうすんの？

---
### データのマイグレーション

---
### メインネット上のゲームのコードを読んでみた

---
### 丸コピー
- ロジックとデータを同じコントラクトに置くと否応無しにこうなる
- データを新しいコントラクトにコピーするタイミングでカラムを変更する
  - FooServiceに対して新しいデータ構造を持つFooServiceV2を作る
  - FooServiceからFooServiceV2にgas limitの範囲内で少しずつデータをコピーするFooServiceMigraterを作成し実行する

---
### ストレージコントラクトとその拡張
- ロジックとデータを扱うコントラクトを分離する
  - データだけを保持するFooServiceStorageをつくりFooServiceからそれを使う
- 追加データはFooServiceStorageExtraを作りそこにおく
  - FooServiceV2はFooServiceStorageとFooServiceStorageExtra両方を見るようにする
- Extra2, Extra3, ... (ToT)

---
### データのマイグレーション：そんなものはなかった
<img src="http://blog-imgs-82.fc2.com/t/a/t/tatibanasan0m0/20150911073754147.jpg">

---
### [protobuf](https://github.com/google/protobuf)ならまともなマイグレーションができるのでは？

---
### protobuf
- googleが開発したIDL
  - 複数のアプリで共有するデータ構造をmessageと呼び、以下のような感じで定義する 
  - =の後の番号が重要
 
```
message Foo {
  uint64 id = 1;
  Nested nested = 2;

  message Nested {
    string text = 1;
    bytes data = 2;
  }
}
```

---
### protobuf
- 定義されたmessageをコンパイルして各種プログラム言語向けのコードができる
- バイナリにシリアライズすることで効率的にデータの移動が行える
  - このフォーマットがナイス

```
p := pb.Foo{
  Id: 1234,
  Nested: *pb.Foo_Nested{
    Text: "hoge",
    Data: []byte{2, 3, 5},
  },
}
out, err := proto.Marshal(p) //out is []byte
```
```
p := &pb.Foo{}
if err := proto.Unmarshal(in, p); err != nil { //in is []byte
        log.Fatalln("Failed to parse Foo:", err)
}
```

---
### protobuf(重要)
- データに割り当てた番号を再利用しない
  - 例えば```uint64 id = 1;```の1は、他のデータの番号としては二度と割当てないようにする
- その条件下で一旦シリアライズされたデータはどのバージョンのコードとも読み込みの互換性があることが保証される

```
message Foo {
  uint64 id = 1; //他のデータで1を使わない
  Nested nested = 2;

  message Nested {
    string text = 1;
    bytes data = 2;
  }
}
```

---
### protobuf
- 例) ```message Foo```に```float f = 3;```を追加した場合
- 古いコードで新しいデータを読む 
  - fに設定されたデータは単に無視される 
- 新しいコードで古いデータを読む
  - fは自明なデフォルト値(0.0f)で初期化されている

```
message Foo {
  uint64 id = 1; 
  Nested nested = 2;
  float f = 3;

  message Nested {
    string text = 1;
    bytes data = 2;
  }
}
```

---
### まともなマイグレーション
- データスキーマをprotobufで定義する。contractにはbytesとして保存
  - versionというカラムを各スキーマに持たせておくのがミソ

```
message Record {
  uint32 version = 1;
  uint32 id = 2;
  //実際のデータカラムたちが続く
}
```

---
### まともなマイグレーション
- FooServiceは各データスキーマの現バージョンを持っておく 
  - スキーマのバージョンを上げる時にはFooServiceの新しいバージョンをデプロイ
- ```public mapping(uint => bytes) entries```とかを持つFooServiceStorageをつくりFooServiceからそれを使うようにしておく

```
contract FooService {
  using pb_Record for pb_Record.Data;
  uint const CURRENT_RECORD_VERSION = 1;
  FooServiceStorage storage;
  ...
}
```

---
### まともなマイグレーション
- データを新しく作るとき、versionカラムには今のバージョンをセットしてFooServiceStorageに保存する
 
```
function newRecord() public returns (uint) {
  pb_Record.Data memory data;
  r.id = newId();
  r.version = CURRENT_RECORD_VERSION;
  storage.entries[r.id] = r.encode();
}
```

---
### まともなマイグレーション
- データを読み出すとき、versionカラムと現在のFooServiceが持っているバージョンが一致していなければ、その差分について必要な初期化を行う
- まともにマイグレーションできそうな感じがしてくる

```
function getRecord(uint id) internal returns (pb_Record.Data r) {
  bytes bs = storage.entries[id];
  r.decode(bs); //バージョンがどう変わっていても問題が生じない
  if (r.version != CURRENT_RECORD_VERSION) {
    //ここで新しいレコードに値をセットしたりする
    r.version = CURRENT_RECORD_VERSION; //最新版になった
    storage.entries[r.id] = r.encode(); //結果を再度保存
  }
}
```

---
### さらなるメリット
- コントラクトからstructを返すのと同等なことがポータブルに実現できる
  - protobufでserializeされたbytesを返す関数を作り、それを受け側でdeserializeする
  - ABIEncoderV2不要&すでに他の実用的な大部分のプログラム言語で可能
  - ABIEncoderV2がprotobufレベルでサポートされるのは時間がかかるだろう(現状はethers.jsぐらい？)
- ABIEncoderV2に対してかなりの間優位性を保てるのでは
    
---
![g](https://image.slidesharecdn.com/fbotnghfqkygplqm1shg-signature-c16e81fc38b2ad401fb3cd0e93a2c0fb3ea50c384f998c312893aabe3df1dc4b-poli-180122053842/95/-7-638.jpg?cb=1516599845)

---
### 問題は

---
### solidityでprotobufって使えるの？
![g](http://up.gc-img.net/post_img/2016/06/4a45h9iVc4PRetF_YOjhe_385.jpeg)

---
### solidityでprotobufって使えるの？
- protobuf verion 2用のコンパイラが[あった](https://github.com/shmookey/solpb)
  - しかしprotobufの現バージョンは3
  - メンテナンスされてない上にhaskellで書かれている
- 2を使う場合、他の部分で苦労が予想される
  - ３年半ほど新しいリリースはされてない
  - protobuf3推奨のものも多い(grpcなど)

---
### 作りました

--- 
### pb3sol
- https://github.com/umegaya/pb3sol
  - pythonでのprotobuf3コンパイラ実装 +  
  - addressなど、solidityのnative typeを利用可
  - jsで上記のnative typeを使いやすくするnpmを用意(soltype-pb)
- よろしければ試してフィードバックください

---
### Thank you!!