It’s now or never

IT系の技術ブログです。気になったこと、勉強したことを備忘録的にまとめて行きます。

【gRPC】gRPCを触ってみた

gRPCについて興味があったため触ってみました。
記載内容については誤りがあるかもしれません。ご指摘ありましたらいただけると幸いです。

gRPCとは

Googleによって開発されたRPCフレームワークです。
そもそもRPCとは(remote procedure call)の略で、なかなか上手く説明できないのですが、 例えばあるマシンからHTTPのプロトコルで決まった形式でのやり取りを行って、別のマシンのプログラム(関数)を実行する。ようなものを指します。
RPCには様々な種類が存在しますが今回は割愛します。(JSON-RPC、XML-RPCなど)
gRPCもRPCの一種になります。

Protocol Buffers

gRPCの特徴の一つとしてデータのシリアライズに「Protocol buffers」という技術が使われています。
これはメッセージデータの構造、手続きの構造を定義するためのインタフェース記述言語(IDL: Interface Description Language)に属するもので「.proto」という拡張子のファイルにメッセージタイプを定義します。
現在「prot3 version」までバージョンが進んでいます。詳しくは公式ページを参照ください。

.protoのサンプル

message Person {
  // 基本は[型名] [名前] でデータを定義するみたいです
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  // enumで固定の数値も定義できる
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

gRPCではこの「.proto」ファイルを各言語のコンパイラ(protocol buffer compiler)でコンパイルすることで、インターフェース処理コードを自動生成します。
APIを自動生成するフレームワークSwaggerがありますがイメージは似ているかもしれません。
※ Swagger使ったことないので間違っているかもしれません。
現在は次のプログラミング言語に対応しているようです。

HTTP/2への対応

gRPCはHTTP/2を前提として定義された規格されています。※ HTTP/2については詳細は割愛します。
HTTP/2を前提としているためクライアントとサーバーの双方向通信において柔軟なやりとりが可能というのもメリットの一つのようです。
環境によっては従来のHTTP/1よりも高パフォーマンスを出すことができるかもしれません。

gRPCを使う時の流れ

では実際にどうやって使えばいいのかを公式のQuick Startに沿ってやってみます。
公式ではいくつかサンプルソースが公開されていますが、今回は「helloworld」サンプルを使用します。
対応言語については、今回は言語はGoを選択しました。

※ 細かな手順は公式の方がわかりやすいと思うので、詳細は上記リンクを参照ください
※ Go言語については理解が足りない部分があるため、間違っていたらすみません

前提

  • Goのバージョンは1.6以上が必要のようです

ツールのインストール

  • gRPCの実装には次のツールが必要なのでインストールします

Helloworld

  • gRPCをインストールするとサンプルソースが幾つか梱包されていて、helloworldも以下に格納されています。
$GOPATH/src/google.golang.org/grpc/examples/helloworld

1. 「.protoファイル」にインターフェースを定義する

  • まずは、protocol buffersを使ったインターフェースを定義します。
syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package helloworld;

service Greeter {
  // rpcというキーワードでgRPCの定義ができる
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  // データ形式と名前でフィールドを定義。後ろの数字はフィールドを固有に識別するためのタグ。ユニークの必要がある。
  // フィールドの識別タグは、1~15は1バイト、16~2047は2バイトでエンコードされるため頻繁に使うフィールドは1~15にすると良いよう
  string name = 1;
}

message HelloReply {
  string message = 1;
}
  • helloworldサンプルのルートディレクトリ配下のhelloworldディレクトリ内に「helloworld.prot」というファイルがあります。
  • サンプルでは次の定義がされています。
    • Greeter: serviceというキーワードで宣言されていて 1つのRPC手続きが定義されています。(メソッド定義のようなもの)
    • HelloRequest: 1つのフィールドをもち、SayHelloの呼び出し時メッセージとして使われます。
    • HelloReply: 1つのフィールドをもち、SayHelloの返却メッセージとして使われます。

※ protocol buffersの仕様については、公式ページを参照ください

2. 定義ファイルをコンパイル

$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld
$ protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld
  • 上記コマンドを実行すると「helloworld.prot」と同じディレクトリに「helloworld.pb.go」というgoファイルが出力されます

「helloworld.pb.go」

  • 色々と自動生成でソースが出力されていますが、詳細は省略して定義した箇所に関連しそうな部分だけ抜粋しています
package helloworld

import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"

import (
    context "golang.org/x/net/context"
    grpc "google.golang.org/grpc"
)

// 定義したフィールドのゲッターメソッド
func (m *HelloRequest) GetName() string {
    if m != nil {
        return m.Name
    }
    return ""
}

// 定義したフィールドのゲッターメソッド
func (m *HelloReply) GetMessage() string {
    if m != nil {
        return m.Message
    }
    return ""
}

/*----------- クライアント側のコード --------------------*/
type GreeterClient interface {
    // 定義したメソッドのクライアント側のインターフェース
    SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}

type greeterClient struct {
    cc *grpc.ClientConn
}

func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
    return &greeterClient{cc}
}

// クライアントが呼び出すSayHelloメソッド
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
    out := new(HelloReply)
    err := grpc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, c.cc, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

/*----------- サーバー側のコード --------------------*/
type GreeterServer interface {
    // 定義したメソッドのサーバー側のインターフェース
    // メソッドの実装は、サーバー側で記述する
    SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}

// サーバーインターフェースを登録する為のメソッド
func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
    s.RegisterService(&_Greeter_serviceDesc, srv)
}
  • この様に対応した言語に合わせてインターフェースのコードが自動生成されます

クライアントの実装

  • 上記自動生成されたgoコードを使って実装されているクライアントのコードを見てみます。

greeter_client/main.go

package main

import (
    "log"
    "os"
    "time"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
  // 生成されたpb.goファイルのimport
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
    address     = "localhost:50051"
    defaultName = "world"
)

func main() {
    // grpcのコネクションを生成
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
  // GreeterのClientの生成
    c := pb.NewGreeterClient(conn)
  // コマンドラインに引数があればそれをnameとして渡す
  // なければ world
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
  // SayHelloメソッドの呼び出し
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.Message)
}
  • クライアン側はgrpcのコネクションとともに生成したクライアントからSayHelloメソッドを呼んでいます。

サーバーの実装

  • 上記自動生成されたgoコードを使って実装されているサーバー側のコードを見てみます。

greeter_server/main.go

package main

import (
    "log"
    "net"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
    "google.golang.org/grpc/reflection"
)

const (
    port = ":50051"
)

// server is used to implement helloworld.GreeterServer.
type server struct{}

// 自動生成されたインターフェースに対してメソッドを実装する
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
  // 自動生成されたHelloReplyを使ってレスポンスを返す
    return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
  // 生成された登録メソッドにgrpcのサーバーを登録する
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    // おそらくこのreflectionにサーバーを登録することでメッセージをハンドリングしてくれる?
    reflection.Register(s)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
  • サーバー側もシンプルでGreeterServerに対してgrpcサーバーを登録するだけのようです。

サンプルを動かす

サーバーの起動

$ go run greeter_server/main.go

クライアントの実行

引数なし

$ go run greeter_client/main.go

出力

2018/05/05 11:56:04 Greeting: Hello world

引数あり

$ go run greeter_client/main.go hoge

出力

2018/05/05 11:56:45 Greeting: Hello hoge

感想

  • 詳細はもう少し勉強する必要がありますが、手順は簡単でした
  • .protoは可読性が高いのでメンテナンス性は良さそうです
  • ソースコードが自動生成されるのも運用的には良さそうです

参考

【Go】メソッドとレシーバ

※ この記事時点のGoのバージョンは「1.10.2」です

Goでは、特定の型に関連づけられた関数を「メソッド」と呼びます。
また、メソッドを呼び出される対象(型)のことを「レシーバ」と呼びます。

inon29.hateblo.jp

ポインタレシーバと変数レシーバ(値レシーバ)

  • メソッドを宣言した時のレシーバの型が「ポインタ」なのか「変数(値)」なのかによってメソッドの挙動が異なります。
  • レシーバがポインタのものを「ポインタレシーバ」変数(値)のものを「変数レシーバ(値レシーバ)」と呼びます。
package main

import (
    "fmt"
)

type Direction struct {
    X, Y float64
}

// 変数レシーバ(値レシーバ)
func (d Direction) Display() {
    fmt.Printf("X: %f, Y: %f\n", d.X, d.Y)
}

// ポインタレシーバ
func (d *Direction) DisplayP() {
    fmt.Printf("X: %f, Y: %f\n", d.X, d.Y)
}

func main() {
    d := Direction{10.0, 3.5}
    d.Display()

    d2 := &Direction{20.0, 50.0}
    d2.DisplayP()
}

ポインタレシーバと変数レシーバの違い

  • 変数レシーバは、メソッド呼び出し時に変数の値をそのままコピーし、新たな値をメソッドに渡します。そのためメソッド内で変数を変更してももとの値は変更されません。
  • ポインタレシーバは、もとの値のポインタを渡すため、メソッド内で変数の値を変更するともとの値が変わります。
    Java(や他の言語)のメソッドの「値渡し」 「参照渡し」と同じです
package main

import (
    "fmt"
)

type Direction struct {
    X, Y float64
}

// 変数レシーバ(値レシーバ)
func (d Direction) Display() {
    // xとyを書き換える
    d.X = 15.0
    d.Y = 5.0
    fmt.Printf("Display() => X: %f, Y: %f\n", d.X, d.Y)
}

// ポインタレシーバ
func (d *Direction) DisplayP() {
    // xとyを書き換える
    d.X = 99.0
    d.Y = 88.0
    fmt.Printf("DisplayP() => X: %f, Y: %f\n", d.X, d.Y)
}

func main() {
    d := Direction{10.0, 3.5}
    d.Display()
    // 元の変数の値は変わらない
    fmt.Printf("main() d => X: %f, Y: %f\n", d.X, d.Y)

    d2 := &Direction{20.0, 50.0}
    d2.DisplayP()
    // 元の変数の値も変わる
    fmt.Printf("main() d2 => X: %f, Y: %f\n", d2.X, d2.Y)
}

出力

Display() => X: 15.000000, Y: 5.000000
main() d => X: 10.000000, Y: 3.500000
DisplayP() => X: 99.000000, Y: 88.000000
main() d2 => X: 99.000000, Y: 88.000000

レシーバの暗黙的型変換

宣言されているレシーバがポインタレシーバである時は、呼び出し元が「変数(値)」または「ポインタ」のどちらであっても実行できます。
変数であったとしてもGoのコンパイラが内部でポインタ型に変換して実行してくれます。

import (
    "fmt"
)

type Direction struct {
    X, Y float64
}

// ポインタレシーバ
func (d *Direction) DisplayP() {
    fmt.Printf("DisplayP() => X: %f, Y: %f\n", d.X, d.Y)
}

func main() {
    d := Direction{10.0, 3.5}
    // ポインタレシーバのメソッドを変数(値)から呼び出せる
    d.DisplayP()
    // コンパイラが(&d)に変換してくれる
    (&d).DisplayP()
}

逆に宣言されているレシーバが変数レシーバ(値レシーバ)である時も、呼び出し元が「変数(値)」または「ポインタ」のどちらであっても実行できます。
この場合はGoのコンパイラが内部で変数に変換してくれています。
※ 変数レシーバであることは変わらないので実行時に渡されるのは呼び出しの変数のコピーになる

package main

import (
    "fmt"
)

type Direction struct {
    X, Y float64
}

// 変数レシーバ(値レシーバ)
func (d Direction) Display() {
    fmt.Printf("Display() => X: %f, Y: %f\n", d.X, d.Y)
}

func main() {
    d := &Direction{20.0, 50.0}
    d.Display()
    // コンパイラが(*d)に変換してくれる
    (*d).Display()
}

構造体がポインタで合っても、変数であっても変わらずドット(.)アクセスできるのと同じように、利便性を考えこのような変換が行われるようです。

参照

【Go】関数とメソッド

最近Goを勉強し始めました。

関数とメソッドの違い

基本的には次のように考えています。

  • 関数は、一般的な関数(funcから始まる)
  • メソッドは「型」に関連付けられた関数

関数

package main

import "fmt"

// これは関数
func Bark() {
    fmt.Println("Bow wow. Woof woof.")
}

func main() {
  // 関数呼び出し
  Bark()
}

出力

Bow wow. Woof woof.

メソッド

package main

import "fmt"

// 型を一つ定義
type Animal struct{ name string }

// これはメソッド
func (animal Animal) Bark() {
    fmt.Printf("[%v] meow.", animal.name)
}

func main() {
    animal := Animal{"Cat"}
    // メソッド呼び出し
    // 型に対してドット(.)でアクセス
    animal.Bark()
}

出力

[Cat] meow.
  • 変数を宣言して、ドット経由で呼び出せます

定義の違い

メソッドの定義は関数定義のfuncの後ろに(<変数> <型>)が追加されています

関数

func Bark() {

メソッド

func (animal Animal) Bark() {
  • この(<変数> <型>)で定義されている関連する型を「レシーバ(receiver)」と呼びます

オブジェクトの表現

Go言語にはクラスという概念がないため、このメソッドとレシーバ(型)との関連でオブジェクトを表現します
※ 言語仕様としてオブジェクトというものは存在しません

例えば、上記サンプルをJavaで表現すると以下のようになります。

class Animal {
  String name;
  
  void bark() {
      System.out.println("Bow wow. Woof woof.");
  }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.bark();
    }
}

Javaのオブジェクト思考の概念では、クラスは「属性(フィールド)」(上ではname)と「操作(メソッド)」(上ではbark)で表現されます。
つまり動物(Animal)というオブジェクトは、名前(name)という属性(データ)で構成されていて、吠える(bark)という操作(振る舞い、動作)をするというような考え方です。

Goの場合は、Javaよりも仕様がシンプルで「型(データ)」がまずあります。
そしてそれは何らかの「関数」によって操作されます。
その特定のデータを操作する特別な関数(振る舞い)を「メソッド」と定義しています。

参考


ちょっとずつ言語仕様を理解していけたらと思います。

【ReDash】AWSのAMIでRedashインスタンスを立ち上げる

EC2インスタンスのセットアップ

  • AMIが提供されているので対象のインスタンスのリンクをクリック
    • クリックすると通常のEC2セットアップ画面に遷移するので環境に合わせた設定でインスタンスを作成する
    • 一応上記リンクではt2.smallが最低として推奨されているようです
      • AMIそのまま起動するとメモリ900M近くを使っているのでsmall以下だと厳しいようです
    • セキュリティグループはhttp(80)とhttps(443)のインバウンドを許可します
  • sshでアクセス
  • とりあえず、パッケージをアップデートする
    • sudo apt-get -u update
    • sudo apt-get -u upgrade
  • redashを最新バージョンにアップデート
    • cd /opt/redash/current
    • sudo bin/upgrade
    • そのまま実行するとAttributeError: 'module' object has no attribute 'SSL_ST_INIT'と出てとまる
    • cd ../redash.<最新バージョン>
    • 依存パッケージをインストール
    • sudo pip install -r requirements_all_ds.txt
    • 完了したら再度以下コマンドでアップデート
    • cd /opt/redash/current
    • sudo bin/upgrade

Redashのセットアップ

  • http://<ip_address>/setupにアクセスするとadmin_userのパスワードを設定できます
  • 右上のデータベースのアイコンを押下し、DataSourcesからNewDataSourceを選択すると接続DBの設定が行えます

【Ethereum】【Solidity】low-level callを使ってコントラクトの関数を実行する

Solidityには、アカウント(アドレス)に対して直接バイトコードを実行する為に、address型の変数に対してcallという関数が用意されています。(low-level callと呼ばれるようです。)

http://solidity.readthedocs.io/en/develop/types.html#members-of-addresses

これは、ConsenSysなどが公開しているMultiSigWalletで使われている仕組みです。
MultiSigWalletは、特定の処理を行う為のコントラクトのアドレスと実行する関数のバイトコードをTransactionとして事前に登録し、規定の人数の承認が得られたタイミングで対象コントラクトの関数を実行する、ということをこのlow-level callを使って実現しています。

具体的な構文としては、以下になります。

<アドレス>.call.(<バイトデータ>)

これを実際にサンプルのContractで動かしてみます。

呼び出し元

pragma solidity ^0.4.17;


contract Caller {

  function callFunc(address destination, bytes data) 
  public
  {
    if (!destination.value(data)) {
      revert();
    }
  }
}
  • 引数destinationは関数を実行する対象のコントラクトのアドレスを指定します
  • dataには関数の実行バイトデータを指定します

実行するターゲットのコントラクト

pragma solidity ^0.4.17;


contract Test {
  uint public num;

  function setNum(uint n) 
  public
  {
    num = n;
  }
}
  • TestコントラクトはsetNumという関数を一つもち、変数numに引数の値をセットしています

テストコード

  • 検証のテストコードは、truffleを使っています。
const Caller = artifacts.require('../contracts/Caller.sol');
const Test = artifacts.require('../contracts/Test.sol');

contract('LowLevelCall', (accounts) => {
  it('Low level call test.', async () => {
    caller = await Caller.new();
    test = await Test.new();
    // 実行前のnumの値
    const before = await test.num();
    console.log("before: " + before);
    // 関数のバイトコードを取り出す
    const data = test.contract.setNum.getData(10);
    console.log("bytecode: " + data);
    // LowLevelCall
    await caller.callFunc(test.address, 0, data);
    // 実行後のnumの値
    const after = await test.num();
    console.log("after: " + after);
  });
});
  • コントラクトのオブジェクトを生成して<コントラクトオブジェクト>.contract.<関数名>.getData(<引数>)バイトコードを取得します
    • ※ truffleの場合は、上記の方法で取得していますが、web3などで取得する場合は若干異なります

テスト実行

truffle(develop)> test
Using network 'develop'.

  Contract: LowLevelCall
before: 0
bytecode: 0xcd16ecbf000000000000000000000000000000000000000000000000000000000000000a
after: 10
    ✓ Low level call test. (202ms)

  1 passing (214ms)
  • Testコントラクトのnum変数が更新されています

同時にEthも送る

call関数は、バイトコードの実行と同時にETHも送ることが可能です。
MultiSigWalletは、その名のとおりWalletなのでバイトコードの実行というよりもこちらの使い方をするほうが多いかと思います。

<アドレス>.call.value(<送信するETH>)(<バイトデータ>)
  • value関数を一緒に呼び出すことETHが送れるようになります
  • これを先程のサンプルコントラクトで試してみます

呼び出し元コントラクトの修正

pragma solidity ^0.4.17;


contract Caller {
  // 引数に送信するETHのvalueを追加
  function callFunc(address destination, uint value, bytes data) 
  public
  {
    if (!destination.call.value(value)(data)) {
      revert();
    }
  }

  // ETHの送信をCaller自体が受け取れる用にpayableの関数を定義
  function () 
  payable public {
  }
}
  • 関数callFuncにETHの送信量を指定する変数valueを追加します
  • コントラクトにETHを送る為のpayableの無名関数を追加します。これはvalue()を実行した時のETHの送り元がCallerコントラクト自身であるため、Callerへ事前にETHを送るためのものです

ターゲットコントラクトの修正

pragma solidity ^0.4.17;


contract Test {
  uint public num;

  // ETHを受け取れる用にpayable修飾子を追加
  function setNum(uint n) 
  payable public
  {
    num = n;
  }
}
  • 実行される関数setNumにpayable修飾子を追加します

テストコード

const Caller = artifacts.require('../contracts/Caller.sol');
const Test = artifacts.require('../contracts/Test.sol');

contract('LowLevelCall', (accounts) => {

  it('Low level call test.', async () => {
    caller = await Caller.new();
    test = await Test.new();
    // 実行前
    const beforeNum = await test.num();
    const beforeBalance = web3.eth.getBalance(test.address);
    console.log("beforeNum: " + beforeNum);
    console.log("beforeBalance: " + beforeBalance);
    // 予めCallerコントラクトに20wei送っておく
    await caller.send(20);
    // 関数のバイトコードを取り出す
    const data = test.contract.setNum.getData(10);
    console.log("bytecode: " + data);
    // LowLevelCall ※10weiも一緒に送る
    await caller.callFunc(test.address, 20, data);
    // 実行後
    const afterNum = await test.num();
    const afterBalance = web3.eth.getBalance(test.address);
    console.log("afterNum: " + afterNum);
    console.log("afterBalance: " + afterBalance);
  });
});
  • まずは、Callerコントラクトに予め送信するための20weiを送っています
  • あとは、callFuncに送信する量(20wei)を追加しているだけです

テスト実行

truffle(develop)> test
Compiling ./contracts/Migrations.sol...
Compiling ./contracts/lowLevelCall/Caller.sol...
Compiling ./contracts/lowLevelCall/Test.sol...

  Contract: LowLevelCall
beforeNum: 0
beforeBalance: 0
bytecode: 0xcd16ecbf000000000000000000000000000000000000000000000000000000000000000a
afterNum: 10
afterBalance: 20

    ✓ Low level call test. (455ms)
  1 passing (466ms)
  • コントラクトTestに20weiが送信されていることが確認できました

【Ethereum】【Solidity0.4.16】viewとpure修飾子

Solidity 0.4.16からviewpureという関数の修飾子が新たに追加されました。

view 修飾子

pragma solidity ^0.4.16;

contract C {
    function f(uint a, uint b) view returns (uint) {
        return a * (b + 42) + now;
    }
}

内部で状態を変更しないことを宣言します。
状態変更の定義は以下となっており、以下の実装が含まれる関数にview修飾子がついていると警告が発生します。

  • 1 . 状態変数を書き換える
  • 2 . eventwを呼び出す
  • 3 . 他のcontractを作成する
  • 4 . selfdestructを使ってcontractの破棄を行う
  • 5 . ethを送る
  • 6 . 関数内部から view または pure 修飾子のつかない関数を呼び出す
  • 7 . low-level callを使用する
  • 8 . opcodeを含むインラインアセンブリを使用する

pure 修飾子

pragma solidity ^0.4.16;

contract C {
    function f(uint a, uint b) pure returns (uint) {
        return a * (b + 42);
    }
}

状態の読み込み、変更を行わないことを宣言します。
view修飾子で記載した状態の変更条件に加え以下を行うことを制限します。

  • 1 . 状態変数を読み込む
  • 2 . this.balance または <address>.balanceへアクセスする
  • 3 . blocktxmsgのメンバにアクセスする(msg.sigとmsg.dataを除く)
  • 4 . 関数内部からpure修飾子のつかない関数を呼び出す
  • 5 . opcodeを含むインラインアセンブリを使用する

今までは、状態を変更しない関数にはconstant修飾子を使用していましたが、今後はviewpureを使い分けて使用していくのが良さそうです。
まだまだSolidityは言語のバージョンアップが早いのでキャッチアップが大変です。

【Ethereum】コンストラクタから外部のコンストラクタの関数を実行する

EthereumのContractにおいて、 Contract内部から他のContractの関数を呼び出したいケースがあります。 その時に考えられる方法としては主に以下の2つかと思います。

  • ①Contract内で別のContractをnewする
  • ②既にデプロイされている外部のContractを使う

今回は、②の方法について検証してみました。

①の場合は特に意識をする必要ないですが、②の場合は既にデプロイされているContractを使うため、外部から値を受け取る必要があり、今回はContractのaddressを受取るという方法をとっています。

デプロイ済みの(共通利用される)Contract

pragma solidity ^0.4.11;

contract MyToken {
    uint256 public totalSupply;

    mapping (address => uint256) public balanceOf;

    function MyToken(address owner) {
        totalSupply = 10000;
        balanceOf[owner] = 1000;
    }

    function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
        if (_to == 0x0) revert();
        if (balanceOf[_from] < _value) revert();
        if (balanceOf[_to] + _value < balanceOf[_to]) revert();
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        return true;
    }
}
  • 簡単なTokenを作成しています
  • このTokenは、送信用の関数としてtransferFromを持っています
  • このTokenは既にデプロイされている想定でこのContractのアドレスを使用します
  • 簡単のため、コンストラクタで渡されるアカウントのアドレスに1000Tokenを渡しています

MyTokenを利用するContract

pragma solidity ^0.4.11;

// 呼び出す対象のコンストラクタはimportする必要がある
import './MyToken.sol';

contract TokenCaller {
    MyToken public token;
  
    function TokenCaller(address _token) {
        require(_token != 0x0);
        // コンストラクタでアドレスからMyTokenObjectに変換
        token = MyToken(_token);
    }

    function transfer(address from, address to, uint256 total) returns (bool) {
        // tokenからtransferFromを実行する
        return token.transferFrom(from, to, total);
    }
}
  • Tokenを利用する側のContractです
  • コンストラクタでTokenのアドレスを受取りTokenのオブジェクトを生成します
  • 関数transferは、生成したTokenのtransferを内部で呼び出しTokenの送信処理を行います

動作確認

動作確認は、truffleを使ってtestRPC上で実行しています。

var TokenCaller = artifacts.require('../contracts/TokenCaller.sol');
var MyToken = artifacts.require('../contracts/MyToken.sol');

contract('TokenCaller', function(accounts) {
    it ('test', async function() {
        // MyTokenのデプロイ
        let token = await MyToken.new(accounts[1])
        // Callerのデプロイ
        let caller = await TokenCaller.new(token.address)
        // Tokenの送り先のaccount[2]にtokenがないことを確認
        console.log(await token.balanceOf.call(accounts[2]));
        // account1からaccount2にトークンを100送る
        await caller.transfer(accounts[1], accounts[2], 100)
        // account2のトークンを確認
        console.log(await token.balanceOf.call(accounts[2]));
    });
});

結果

  Contract: TokenCaller
{ [String: '0'] s: 1, e: 0, c: [ 0 ] }
{ [String: '100'] s: 1, e: 2, c: [ 100 ] }
  • account2にトークンが送られていることが確認できました。

参考

https://dappsforbeginners.wordpress.com/tutorials/interactions-between-contracts/