続・無能日記 http://blog.potix.jp/ 無能なpotiが適当に綴る本当にダメダメな日記 en-us Sat, 05 Aug 2017 00:00:00 +0900 http://blog.potix.jp/2017/08/05/solidity.html http://blog.potix.jp/2017/08/05/solidity.html <![CDATA[solidity勉強 (2)]]> solidity勉強 (2)

前の続き

可視レベル

可視レベルとして、external, public, internal privateが指定できる。 ステート変数と関数でデフォルト値は違う。 ステート変数の場合internal(javaでいうprotected)がデフォルト値。 関数はpublicがデフォルト値。

詳細は以下を参照。

複数の値の返却

solidityはpythonのtupleのように複数の値を返却できる。

pragma solidity ^0.4.11;

contract test {
    function tuple() private returns (int, string, address) {
        return (1, "hello", msg.sender);
    }

    function vars() returns (int, string, address) {
        var (_i, _message, _sender) = tuple();
        return (_i + 1, _message, _sender);
    }

}

// vars
// Transaction cost: 22238 gas.
// Execution cost: 966 gas.
// Decoded:
//   int256: 2
//   string: hello
//   address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c

abstract contract

javaのabstractと同じ。 実装が欠けてるとデプロイできない。

pragma solidity ^0.4.11;

contract abstractContract {
    string name = "hoge";

    function getName() returns (string);
}


contract test is abstractContract {
    function getName() returns (string) {
        return name;
    }
}

// getName
// Transaction cost: 22736 gas.
// Execution cost: 1464 gas.
// Decoded:
//   string: hoge

interface

javaのインターフェイスと同じ。 インターフェイスを満たしていないとdeployができない。 abstrct contractとの違いは以下。

  • 他のコントラクトやインターフェイスを継承できない
  • コンストラクタを定義できない
  • 変数、struct、enumを定義できない
pragma solidity ^0.4.11;

contract testInterface {
    function getName() returns (string);
    function getMessage() returns (string);
}

contract test is testInterface {
    string name;
    string message;

    function test() {
        name = "alice";
        message = "bob";
    }

    function getName() returns (string) {
        return name;
    }

    function getMessage() returns (string) {
        return message;
    }

}

arrayの返却

arrayを返却することもできる。ただし、多次元配列やstructのarrayは返却できない。 stringは2次元配列扱いなのでできない。

pragma solidity ^0.4.11;

contract testInt {
    function returnIntArray() returns (int[]) {
        int[] memory a = new int[](3);
        a[0] = 1;
        a[1] = 10;
        a[2] = 100;
        return a;
    }
}

// returnIntArray
// Transaction cost: 22269 gas.
// Execution cost: 997 gas.
// Decoded:
//   int256[]: 1, 10, 100

contract testByte {
    function returnByteArray() returns (byte[]) {
        byte[] memory a = new byte[](3);
        a[0] = byte(1);
        a[1] = byte(10);
        a[2] = byte(100);
        return a;
    }
}

// returnByteArray
// Transaction cost: 22365 gas.
// Execution cost: 1093 gas.
// Decoded:
//   bytes1[]: 0x01, 0x0a, 0x64


// contract testString {
//     // compile error
//     // 2次元配列扱い
//     function returnStringArray() returns (string[]) {
//         string[] memory a = new string[](3);
//         a[0] = 1;
//         a[1] = 10;
//         a[2] = 100;
//         return a;
//     }
// }

// contract testStruct {
//     struct value {
//         int v;
//     }
//     // compile error
//     // structはだめ
//     function returnStructArray() returns (value[]) {
//         value[] memory a = new value[](3);
//         a[0].v = 1;
//         a[1].v = 10;
//         a[2].v = 100;
//         return a;
//     }
// }

constant

状態が変更しないこと明示的に指定する。変数や関数につけられる。 関数では主に状態変更をしないgetterにつけておく。 constantな関数はweb3.js等でmethods.myMethod.callを使うことで、ローカルノードで処理を完結できるためgasコスト無しで処理ができる。ただし、ローカルノードの状態に依存する。

現状、状態を変更する関数にconstantをつけてもコンパイラが怒ってくれないので注意。この状態で実行すると謎の挙動になる。

pragma solidity ^0.4.11;

contract test {
    string name;
    string message;

    function test() {
        name = "alice";
        message = "hello";
    }

    function getName() constant returns (string) {
        return name;
    }

    function getMessage() constant returns (string) {
        return message;
    }

    function getAndUpdateMessage() constant returns (string) {
        var m = message;
        name = "bob";
        message = "zzz";
        return m;
    }
}

// getName
// Transaction cost: 22758 gas. (caveat)
// Execution cost: 1486 gas.
// Decoded:
//   string: alice
// getMessage
// Transaction cost: 22780 gas. (caveat)
// Execution cost: 1508 gas.
// Decoded:
//   string: hello
// getAndUpdateMessage
// Transaction cost: 44069 gas. (caveat)
// Execution cost: 22797 gas.
// Decoded:
//   string: zzz

アドレスからコントラクトへの変換

<コントラクト名>(<コントラクトのアドレス>)でアドレスをコントラクトに変換することができる。 変換したコントラクトの関数を呼び出した場合、address.call同様にコンテキストや呼び出し元が変更されているので注意。

pragma solidity ^0.4.11;

contract A {
    int value = 100;

    function getValue() returns (int, address, address) {
        return (value, this, msg.sender);
    }
}

contract B {
    string message = "zzz";
    address contractA;

    function B(address _contractA) {
        contractA = _contractA;
    }

    function getValue() returns (int, string, address, address, address, address){
        message = "hello";
        var (_v, _this, _sender) = A(contractA).getValue();
        return (_v, message, this, msg.sender, _this, _sender);
    }
}

// 1. Aのコントラクトを作ってアドレスを取得する
//    output: 0x8609a0806279c94bcc5432e36b57281b3d524b9b
// 2. 1で取得したアドレスを指定してBのコントラクトを作る
//    input: "0x8609a0806279c94bcc5432e36b57281b3d524b9b"
//
// getValue
// Transaction cost: 35962 gas.
// Execution cost: 14690 gas.
// Decoded:
// int256: 100
// string: hello
// address: 0x9876e235a87f520c827317a8987c9e1fde804485
// address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
// address: 0x8609a0806279c94bcc5432e36b57281b3d524b9b
// address: 0x9876e235a87f520c827317a8987c9e1fde804485

thisやsenderが変わっていることが確認できる。次に、呼び出した関数でrevert()を読んでみる。

pragma solidity ^0.4.11;

contract A {
    int value = 100;

    function getValue() returns (int, address, address) {
        revert();
        return (value, this, msg.sender);
    }
}

contract B {
    string message = "zzz";
    address contractA;

    function B(address _contractA) {
        contractA = _contractA;
    }

    function getValue() returns (int, string, address, address, address, address){
        message = "hello";
        var (_v, _this, _sender) = A(contractA).getValue();
        return (_v, message, this, msg.sender, _this, _sender);
    }
}

// getValue
// Exception during execution.

この場合は、例外になる。address.callのように自前で例外の処理をする必要はない。

contractの変更

一度deployしたcontractを変更することはできない。ベースがブロックチェーンなので、記録はできても消したり変更したりはできない。そこで、変更が必要になった時のことを考えてコントラクトを作る必要がある。

  • 変更可能なコントラクトを作るポイント
    • ストレージ用とロジック用のコントラクトを分ける
    • ストレージコントラクトを汎用的に作っておく
    • インターフェイス定義をしておいて、変更があってもインターフェイスは保障されるようにする

これまでのことを踏まえつつ、contractを作ると以下のような感じになる。

pragma solidity ^0.4.11;

contract Ownable {
  address public owner;

  function Ownable() {
    owner = msg.sender;
  }

  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }
}

contract storageContract is Ownable {
    address _this;
    address _sender;

    mapping (address => bool) allowContracts;

    function setAllowContracts(address _allowContract) onlyOwner {
        allowContracts[_allowContract] = true;
    }

    function checkAllowContract() private returns (bool) {
        return (allowContracts[msg.sender] == true);
    }

    mapping(bytes32 => uint) idMap;

    function getAndIncrementId(bytes32 key) returns (uint, bool) {
        if (!checkAllowContract()) {
            return (0, false);
        }
        var _v = idMap[key];
        idMap[key] = _v + 1;
        return (_v, true);
    }

    function getMaxId(bytes32 key) constant returns (uint) {
        return idMap[key];
    }

    function getIdArray(bytes32 key) constant returns (uint[]) {
        var _maxId = idMap[key];
        uint[] memory ids = new uint[](_maxId);
        for (uint i = 0; i < _maxId; i++) {
            ids[i] = i;
        }
        return ids;
    }

    mapping(bytes32 => string) stringMap;

    function setString(bytes32 key, string value) returns (bool) {
        if (!checkAllowContract()) {
            return false;
        }
        stringMap[key] = value;
        // thisはstorageContract
        _this = this;
        // msg.senderはmasterContract
        _sender = msg.sender;
        return true;
    }

    function getString(bytes32 key) constant returns (string) {
        return stringMap[key];
    }

    // this, sender確認用。不要。
    function getThisSender() returns (address, address) {
        return (_this, _sender);
    }
}

library logicLibrary {
    int constant NG = 0;
    int constant OK = 1;

    function checkName(string _name) returns (bool) {
        if (bytes(_name).length == 0) {
            return false;
        }
        return true;
    }

    function setUser(address _storage, string _name, string _email) internal returns (uint, int) {
        if (!checkName(_name)) {
            return (0, NG);
        }
        var (_userId, _ok) = storageContract(_storage).getAndIncrementId(sha3("userId"));
        if (!_ok) {
            return (0, NG);
        }
        _ok = storageContract(_storage).setString(sha3("user.name", _userId), _name);
        if (!_ok) {
            return (0, NG);
        }
        _ok = storageContract(_storage).setString(sha3("user.email", _userId), _email);
        if (!_ok) {
            return (0, NG);
        }
        return (_userId, OK);
    }
}

contract masterContractInterface {
    function setUser(string _name, string _email) returns (uint, int);
}

contract mainContract is masterContractInterface {
    using logicLibrary for address;
    address externalStorage;
    address _this;
    address _sender;

    function mainContract(address _externalStorage) {
        require(_externalStorage != address(0));
        externalStorage = _externalStorage;
    }

    function setUser(string _name, string _email) returns (uint, int) {
        // thisはmasterContract
        _this = this;
        // msg.senderはこの関数を呼び出したコントラクトのアドレス
        _sender = msg.sender;
        return externalStorage.setUser(_name, _email);
    }

    // sha3の値が知りたいだけ、クライアント側で計算すれば不要
    function getIdKey(string _key) returns (bytes32) {
        return sha3(_key);
    }
    function getValueKey(string _key, uint _userId) returns (bytes32) {
        return sha3(_key, _userId);
    }

    // this, sender確認用。不要。
    function getThisSender() returns (address, address) {
        return (_this, _sender);
    }
}

// 1. storageContractをdeployしてアドレスを取得
//      output: "0x11f0c46dc617619d51aa60e02daca7608dc714e7"
// 2. mainContractをdeplayしてアドレスを取得
//      input:  "0x11f0c46dc617619d51aa60e02daca7608dc714e7"
//      output: "0x91e3e0cd9afa1a0ced2456214a2e544a4f5a1db2"
// 3. storageContractのsetAllowContractでmainContractのアドレスを設定
//      input:  "0x91e3e0cd9afa1a0ced2456214a2e544a4f5a1db2"
// 4. mainContractのsetUserで一人目のユーザーを登録
//      input:  "user1", "email1"
//      output  0, 1
// 5. mainContractのsetUserで二人目のユーザーを登録
//      input:  "user2", "email2"
//      output  1, 1
// 6. mainContractのgetIdKeyで"userId"のsha3の値を取得
//      input:  "userId"
//      output: 0x27c962f3b25d487c0ce42b202affbfd86a5cc45d430047bd3644438ed354eaaa
// 7. storageContractのgetIdArrayでuserIdリストを確認
//      input:  "0x27c962f3b25d487c0ce42b202affbfd86a5cc45d430047bd3644438ed354eaaa"
//      output: 0, 1
// 8. mainContractのgetValueKeyで"user.name", 0のsha3の値を取得
//      input: "user.name", 0
//      output: 0x68582ccb44bf56e47bc7080f480a1889bc73a0c06b5be9bcca501755eb67fc91
// 9. mainContractのgetValueKeyで"user.email", 1のsha3の値を取得
//      input: "user.email", 1
//      output: 0xf8c4454c0d4b8625ee2090e89210f762544f806f2ca8d8c9c5b43b12acf64b50
// 10. storageContractのgetStringでuserId 0のユーザ名を取得
//      input: "0x68582ccb44bf56e47bc7080f480a1889bc73a0c06b5be9bcca501755eb67fc91"
//      output: user1
// 11. storageContractのgetStringでuserId 1のメールアドレスを取得
//      input: "0xf8c4454c0d4b8625ee2090e89210f762544f806f2ca8d8c9c5b43b12acf64b50"
//      output: email2

ここで問題なのは、ライブラリのビジネスロジックだけを更新したい場合どうすればいいのかという問題が出てくるのだけど、どうしたらいいのだろう。。。 基本的に、DB操作とビジネスロジックは近くにあって欲しいし、delegatecallとか使い倒すとそれはそれでgasコストのオーバヘッドが出てくる。

単純に思いつく方法としては、機能単位にコントラクトとライブラリのセットを細分化しておけば、変更が必要になった場合の影響が小さくなることが考えられるぐらい。

以下のサイトでもライブラリのアップグレードをどうするか考えているみたい。

]]>
Sat, 05 Aug 2017 00:00:00 +0900
http://blog.potix.jp/2017/08/04/solidity.html http://blog.potix.jp/2017/08/04/solidity.html <![CDATA[solidity勉強]]> solidity勉強

細かい話はドキュメントを参照してもらうのが一番正確で早い。

メッセージ

ethreumにはトランザクションとメッセージがある。詳細は以下。

ちなみに、solidityのドキュメントで言うトランザクションはethreumのホワイトペーパのtransactionを含むメッセージ全般のことを言っている。 solidityのコード上msgという特別な変数が出てくるがこれは、このメッセージ全般を指す。

gas

contractのdeploy(トランザクション)やcontract内の関数の実行(メッセージ)にはノードのリソースを消費するためgas料金が発生する。つまり、リアルにお金がかかるということ。ちなみにgas料金は変動していて、今のgas料金がどれぐらいかは、以下からわかる。

gas limit

どれだけgasを使うかを指定する。gasが足りない場合は処理が中断される。トランザクションは巻き戻る。 無限ループ防止のために必要。

単位

一般的にethの単位がよく知られているが、一番最小の単位はwei。solidityのmsg.valueもweiを基準にしている。 そのほかにもbabbageとかlovelaceとかshannonとかszaboとかfinneyとかある。

var unitMap = {
    'noether':      '0',
    'wei':          '1',
    'kwei':         '1000',
    'Kwei':         '1000',
    'babbage':      '1000',
    'femtoether':   '1000',
    'mwei':         '1000000',
    'Mwei':         '1000000',
    'lovelace':     '1000000',
    'picoether':    '1000000',
    'gwei':         '1000000000',
    'Gwei':         '1000000000',
    'shannon':      '1000000000',
    'nanoether':    '1000000000',
    'nano':         '1000000000',
    'szabo':        '1000000000000',
    'microether':   '1000000000000',
    'micro':        '1000000000000',
    'finney':       '1000000000000000',
    'milliether':   '1000000000000000',
    'milli':        '1000000000000000',
    'ether':        '1000000000000000000',
    'kether':       '1000000000000000000000',
    'grand':        '1000000000000000000000',
    'mether':       '1000000000000000000000000',
    'gether':       '1000000000000000000000000000',
    'tether':       '1000000000000000000000000000000'
};

変数の格納場所

変数の格納場所はstorage, memory stackの3種類がある。

  • storage
    • 永続化される高価な領域
  • memory
    • storageより安い揮発性領域
  • stack
    • memoryより安い
    • 1関数に16変数しか定義できないほど小さな領域

  • address
    • ethreumのアドレスを入れる型
  • int, uint
    • int256のalias
    • uint256のalias
  • int8,..(8bit増やし)..,int256
  • byte
    • bytes1のalias
  • bytes1,..(1バイト増やし)..,bytes32
  • string
  • bytes
    • byte[]と同じようなもの
  • array
    • <型>[]で表す
    • storage領域であればpushを使って動的にサイズを拡張できる
  • map
    • mapping(<型> => <型>) で表す

msg

メッセージの情報が格納された変数。

  • msg.sender
    • メッセージ送信者のアドレス
  • msg.value
    • メッセージと一緒に送ったwei数

より詳細は、以下参照

block

ブロックチェーンのブロックにまつわる情報が格納された変数。

  • block.coinbase
    • コインベースを返す
  • block.number
    • 現在のブロック番号
  • block.timestamp
    • 現在のブロックのタイムスタンプ

より詳細は、以下参照

sha3()

keccak-256ダイジェストを取る関数。bytes32が返ってくる。

revert()

状態変更を巻き戻して実行を止める。

require()/assert()

どちらも条件がfalseの場合にrevert()を行うものだが、requireは入力が不正な場合、assertは内部的にあり得ない場合に使う。

Contract

classのようなもの。deployすると、deployしたcontractに対してアドレスが割り当てられる。 継承可能。contract名と同じ関数がコンストラクタ。

pragma solidity ^0.4.11;

contract test {
    string message;
    string name;

    function test() {
        message = "hello";
        name = "bob";
    }

    function getMessage() returns (string) {
        return message;
    }

    function getName() returns (string) {
        return name;
    }
}

contract test2 is test {
    string test;

    function test2() {
        test = "test";
        name = "alice";
    }

    function getAll() returns (string, string, string) {
        return (test, message, name);
    }
}

// getAll
//   Transaction cost: 25430 gas.
//   Execution cost: 4158 gas.
//   Decoded:
//     string: test
//     string: hello
//     string: alice
// getMessage
//   Transaction cost: 22780 gas.
//   Execution cost: 1508 gas.
//   Decoded:
//     string: hello
// getName
//   Transaction cost: 22736 gas.
//   Execution cost: 1464 gas.
//   Decoded:
//     string: alice

Modifier

pythonのデコレータのようなもの。”_”にラップした関数が挿入されているイメージ。 主に事前の条件チェックに使われる。継承で上書することもできる。

pragma solidity ^0.4.11;

contract test {
    string message;
    string name;
    bool ok = true;

    function test() {
        message = "hello";
        name = "bob";
    }

    modifier isOK() {
        assert(ok);
        _; // ここに関数本体が挿入されている
        // _ の後ろに何か書いてもOK
    }

    function getMessage() isOK returns (string) {
        return message;
    }

    function getName() isOK returns (string) {
        return name;
    }
}

contract test2 is test {
    string t;

    function test2() {
        t = "test";
        name = "alice";
        ok = false;
    }

    function getAll() isOK returns (string, string, string) {
        return (t, message, name);
    }
}

// getAll
//   Exception during execution.
// getMessage
//   Exception during execution.
// getName
//   Exception during execution.

address.call()

低レベルな呼び出し。別のコントラクトの関数を呼び出したりできる。 特に使う必要がないなら使わないのが無難。

  • 危険ポイント

    • msg.senderが変わる
    • reentrantが発生する可能性がある
      • 同じ契約の関数を呼び出した場合等
    • 呼び出された側でrevert()しても呼び出し側は自動でrevert処理をしない
      • 例外を伝播できなため
pragma solidity ^0.4.11;

contract test {
    string attr;
    string message;
    string name;
    address sender;

    function test() {
        attr = "hehehe";
        message = "hello";
        name = "alice";
    }

    function first() returns (string, string, string, string, address, address) {
        attr = "hahaha";
        if (this.call(bytes4(sha3("second()")))) {
            return ("success", attr, message, name, msg.sender, sender);
        } else {
            return ("error", attr, message, name, msg.sender, sender);
        }
    }

    function second()  {
        name = "bob";
        message = "zzz";
        sender = msg.sender;
    }

}

// first
// Transaction cost: 79543 gas.
// Execution cost: 58271 gas.
// Decoded:
//   string: success
//   string: hahaha
//   string: zzz
//   string: bob
//   address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
//   address: 0xdc544654fefd1a458eb24064a6c958b14e579154

返却された値のaddressが変わっていることが確認できる。 次にsecond()内でrevert()をしてみる。

pragma solidity ^0.4.11;

contract test {
    string attr;
    string message;
    string name;
    address sender;

    function test() {
        attr = "hehehe";
        message = "hello";
        name = "alice";
    }

    function first() returns (string, string, string, string, address, address) {
        attr = "hahaha";
        if (this.call(bytes4(sha3("second()")))) {
            return ("success", attr, message, name, msg.sender, sender);
        } else {
            return ("error", attr, message, name, msg.sender, sender);
        }
    }

    function second()  {
        name = "bob";
        message = "zzz";
        sender = msg.sender;
        revert();
    }

}

// first
// Transaction cost: 2958284 gas.
// Execution cost: 2937012 gas.
// Decoded:
//   string: error
//   string: hahaha
//   string: hello
//   string: alice
//   address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
//   address: 0x0

firstで変更したattrの値は変わっていないことが確認できる。

address.delegatecall()

address.call()との違いはコンテキストの違いにある。 delegatecallは呼び出し元の契約がコンテキストとなる。 callは呼び出された契約がコンテキストとなる。 つまり、別のコントラクトの関数を借りてきて使うことができる。

  • call()の場合
pragma solidity ^0.4.11;

contract a {
    string name;
    address self;
    address sender;

    function a() {
        name = "bob";
    }

    function update() {
        name = "alice";
        self = this;
        sender = msg.sender;
    }

    function getA() returns (string, address, address) {
        return (name, self, sender);
    }
}

contract b {
    string name;
    address contractA;

    function b(address _contractA) {
        contractA = _contractA;
        name = "jon";
    }

    function call() returns (string, string, address, address, address) {
        name = "smith";
        if (contractA.call(bytes4(sha3("update()")))) {
            return ("success", name, this, contractA, msg.sender);
        } else {
            return ("error", name, this, contractA, msg.sender);
        }
    }
}

// call
// Transaction cost: 86749 gas.
// Execution cost: 65477 gas.
// Decoded:
//   string: success
//   string: smith
//   address: 0x375eae23b65feb1833072328647902f1fe9afa61
//   address: 0xa36c60652b04ec2c04221eab0401fb91132f7a06
//   address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
// getA
// Transaction cost: 23266 gas.
// Execution cost: 2058 gas.
// Decoded:
//   string: alice
//   address: 0xa36c60652b04ec2c04221eab0401fb91132f7a06
//   address: 0x375eae23b65feb1833072328647902f1fe9afa61

selfがcontractAを指していて、msg.senderが呼び出し元コントラクトになっていることがわかる。

  • delegatecallの場合
pragma solidity ^0.4.11;

contract a {
    string name;
    address self;
    address sender;

    function a() {
        name = "bob";
    }

    function update() {
        name = "alice";
        self = this;
        sender = msg.sender;
    }
}

contract b {
    string name;
    address self;
    address sender;
    address contractA;

    function b(address _contractA) {
        contractA = _contractA;
        name = "jon";
    }

    function call() returns (string, string, address, address, address, address, address) {
        name = "smith";
        if (contractA.delegatecall(bytes4(sha3("update()")))) {
            return ("success", name, this, contractA, msg.sender, self, sender);
        } else {
            return ("error", name, this, contractA, msg.sender, self, sender);
        }
    }
}

// call
// Transaction cost: 87368 gas.
// Execution cost: 66096 gas.
// Decoded:
//   string: success
//   string: alice
//   address: 0xe37538be3aee714cef0e8a11c0227d2240fec16f
//   address: 0x22e37c29ad8303c6b58d3cea5a3f86160278af01
//   address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
//   address: 0xe37538be3aee714cef0e8a11c0227d2240fec16f
//   address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c

thisやmsg.senderが変わっていないことがわかる。 次に、delegatecallする関数にrevert()を仕込んでみる。

pragma solidity ^0.4.11;

contract a {
    string name;
    address self;
    address sender;

    function a() {
        name = "bob";
    }

    function update() {
        name = "alice";
        self = this;
        sender = msg.sender;
        revert();
    }
}

contract b {
    string name;
    address self;
    address sender;
    address contractA;

    function b(address _contractA) {
        contractA = _contractA;
        name = "jon";
    }

    function call() returns (string, string, address, address, address, address, address) {
        name = "smith";
        if (contractA.delegatecall(bytes4(sha3("update()")))) {
            return ("success", name, this, contractA, msg.sender, self, sender);
        } else {
            return ("error", name, this, contractA, msg.sender, self, sender);
        }
    }
}

// call
// Exception during execution.

関数を借りてきているだけなので、ちゃんとexceptionで中止される。

Library

前に説明したdelegatecallやJUMP命令が内部的に使われている。 主にロジックを外に出すときに使う。 internalがついていればJUMP命令が実行され、ついてなければDELEGATECALL命令が実行される。

usingで型にライブラリ関数をバインドできる。バインドするとpythonのselfのように扱えるようになる。 <型の変数>.<関数>()で呼び出しができるようになり、ライブラリ関数の一つ目の引数が”型の変数”になる。

pragma solidity ^0.4.11;

library who {
    struct person {
        string name;
    }

    function init (string _name) internal returns (person) {
        person memory _person;
        _person.name = _name;
        return _person;
    }

    function getName (person _person) internal returns (string) {
        return _person.name;
    }

    function echoBool (bool b) returns (bool) {
        return b;
    }

}

contract test {
    using who for who.person;

    who.person person;

    function test() {
        person = who.init("hoge");
    }

    function getName() returns (string) {
        // JUMP命令
        return person.getName();
    }

    function echoBool () returns (bool) {
        // DELEGATECALL命令
        return who.echoBool(true);
    }
}

// echoBool
// Transaction cost: 23267 gas.
// Execution cost: 1995 gas.
// Decoded:
//   bool: true
// getName
// Transaction cost: 22928 gas.
// Execution cost: 1656 gas.
// Decoded:
//  string: hoge

gas代検証

  • まとめ
    • ストレージの使用は必要最低限にする
    • データ量は必要最低限にする
    • structやarrayを使うよりもmappingをうまく使った方がリーズナブル
      • 割り切って mapping(byte32 => string) を一つ用意してsha3()でkeyを作って入れていく方がリーズナブル
    • loopは重いし、高価だから極力使わない
    • a[x][y]みたいな呼び出しを何度もやるより、var v = a[x][y]で変数化してvを使い回す方がリーズナブル
  • データ量
pragma solidity ^0.4.11;

contract gasTestInputChange {
    string value;

    function setString(string _value){
        value = _value;
    }

    function getString() returns (string) {
        return value;
    }
}

// input "aaa"
// setString
//   Transaction cost: 42677 gas.
//   Execution cost: 20701 gas.
// getString
//   Transaction cost: 22268 gas.
//   Execution cost: 996 gas.

// input "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
// setString
//   Transaction cost: 710010 gas.
//   Execution cost: 623010 gas.
// getString
//   Transaction cost: 27568 gas.
//   Execution cost: 6296 gas.
  • ストレージとストラクとマッピング
pragma solidity ^0.4.11;

contract gasTestMultistorage {
    string value1;
    string value2;
    string value3;

    function setString(string _value1, string _value2, string _value3){
        value1 = _value1;
        value2 = _value2;
        value3 = _value3;
    }

    function getString() returns (string, string, string) {
        return (value1, value2, value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString
//   Transaction cost: 85273 gas.
//   Execution cost: 61889 gas.
// getString
//   Transaction cost: 23938 gas.
//   Execution cost: 2666 gas.

contract gasTestStruct1 {
    struct value {
        string value1;
        string value2;
        string value3;
    }

    value v;

    function setString(string _value1, string _value2, string _value3){
        v = value({
            value1: _value1,
            value2: _value2,
            value3: _value3
        });
    }

    function getString() returns (string, string, string) {
        return (v.value1, v.value2, v.value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString
//   Transaction cost: 85405 gas.
//   Execution cost: 62021 gas.
// getString
//   Transaction cost: 23956 gas.
//   Execution cost: 2684 gas.

contract gasTestStruct2 {
    struct value {
        string value1;
        string value2;
        string value3;
    }

    value v;

    function setString(string _value1, string _value2, string _value3){
        v.value1 = _value1;
        v.value2 = _value2;
        v.value3 = _value3;
    }

    function getString() returns (string, string, string) {
        return (v.value1, v.value2, v.value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString
//   Transaction cost: 85291 gas.
//   Execution cost: 61907 gas.
// getString
//   Transaction cost: 23956 gas.
//   Execution cost: 2684 gas.

contract getTestStatuctMapping {
    struct value {
        string value1;
        string value2;
        string value3;
    }
    value sv;
    mapping (string => string) mv;

    function setStruct(string _value1, string _value2, string _value3) {
        sv.value1 = _value1;
        sv.value2 = _value2;
        sv.value3 = _value3;
    }

    function getStruct() returns (string _value1, string _value2, string _value3) {
        return (sv.value1, sv.value2 = _value2, sv.value3);
    }

    function setMapping(string _value1, string _value2, string _value3) {
        mv["value1"] = _value1;
        mv["value2"] = _value2;
        mv["value3"] = _value3;
    }

    function getMapping() returns (string _value1, string _value2, string _value3) {
        return (mv["value1"], mv["value2"], mv["value2"]);
    }

}

// input "aaa", "bbb", "ccc"
// setStruct
//    Transaction cost: 85291 gas.
//    Execution cost: 61907 gas.
// setMapping
//    Transaction cost: 85628 gas.
//    Execution cost: 62244 gas.
// getStruct
//    Transaction cost: 29070 gas.
//    Execution cost: 7798 gas.
// getMapping
//    Transaction cost: 24331 gas.
//    Execution cost: 3059 gas.
  • もう少し難しい構造
pragma solidity ^0.4.11;

contract gasTestMapping {
    struct value {
        string value1;
        string value2;
        string value3;
    }

    mapping(address => value) values;

    function setString1(string _value1, string _value2, string _value3){
        values[msg.sender].value1 = _value1;
        values[msg.sender].value2 = _value2;
        values[msg.sender].value3 = _value3;
    }

    function setString2(string _value1, string _value2, string _value3){
        var v = values[msg.sender];
        v.value1 = _value1;
        v.value2 = _value2;
        v.value3 = _value3;
    }

    function setString3(string _value1, string _value2, string _value3){
        values[msg.sender] = value({
           value1: _value1,
           value2: _value2,
           value3: _value3
        });
    }

    function getString1() returns (string, string, string) {
        return (values[msg.sender].value1,  values[msg.sender].value2, values[msg.sender].value3);
    }

    function getString2() returns (string, string, string) {
        var v = values[msg.sender];
        return (v.value1, v.value2, v.value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString1
//     Transaction cost: 85558 gas.
//     Execution cost: 62174 gas.
// setString2
//     Transaction cost: 55608 gas.
//     Execution cost: 32224 gas.
// setString3
//     Transaction cost: 55731 gas.
//     Execution cost: 32347 gas.
// getString1
//     Transaction cost: 24267 gas.
//     Execution cost: 2995 gas.
// getString2
//     Transaction cost: 24058 gas.
//     Execution cost: 2786 gas.

contract gasTestArray {
    struct value {
        string value1;
        string value2;
        string value3;
    }
    value[] values;
    uint valuesIndex;

    function getInitLength() returns (uint) {
        return values.length;
    }

    function setString1(string _value1, string _value2, string _value3){
        values.push(value({
           value1: _value1,
           value2: _value2,
           value3: _value3
        }));
    }

    function getString1() returns (string, string, string) {
        return (values[0].value1,  values[0].value2, values[0].value3);
    }

    function getString2() returns (string, string, string) {
        var v = values[0];
        return (v.value1, v.value2, v.value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString1
//    Transaction cost: 105844 gas.
//    Execution cost: 82460 gas.
// getString1
//    Transaction cost: 24459 gas.
//    Execution cost: 3187 gas.
// getString2
//    Transaction cost: 24122 gas.
//    Execution cost: 2850 gas.

contract gasTestMappingIndex {
    struct value {
        string value1;
        string value2;
        string value3;
    }

    mapping(uint => value) values;
    uint valuesIndex;

    function preSetString1(string _value1, string _value2, string _value3){
        values[valuesIndex].value1 = _value1;
        values[valuesIndex].value2 = _value2;
        values[valuesIndex].value3 = _value3;
    }

    function setString1(string _value1, string _value2, string _value3){
        values[valuesIndex].value1 = _value1;
        values[valuesIndex].value2 = _value2;
        values[valuesIndex].value3 = _value3;
        valuesIndex++;
    }

    function setString2(string _value1, string _value2, string _value3){
        var v = values[valuesIndex];
        v.value1 = _value1;
        v.value2 = _value2;
        v.value3 = _value3;
        valuesIndex++;
    }

    function setString3(string _value1, string _value2, string _value3){
        values[valuesIndex] = value({
           value1: _value1,
           value2: _value2,
           value3: _value3
        });
        valuesIndex++;
    }

    function getString1() returns (string, string, string) {
        return (values[valuesIndex].value1,  values[valuesIndex].value2, values[valuesIndex].value3);
    }

    function getString2() returns (string, string, string) {
        var v = values[valuesIndex];
        return (v.value1, v.value2, v.value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString1
//    Transaction cost: 105762 gas.
//    Execution cost: 82378 gas.
// setString2
//    Transaction cost: 90563 gas.
//    Execution cost: 67179 gas.
// setString3
//    Transaction cost: 90686 gas.
//    Execution cost: 67302 gas.
// getString1
//    Transaction cost: 24424 gas.
//    Execution cost: 3152 gas.
// getString2
//    Transaction cost: 24115 gas.
//    Execution cost: 2843 gas.


contract gasTestMappingSha3 {

    mapping(bytes32 => string) values;
    uint valuesIndex;

    function preSetString1(string _value1, string _value2, string _value3){
        values[sha3(valuesIndex, "value1")] = _value1;
        values[sha3(valuesIndex, "value2")] = _value2;
        values[sha3(valuesIndex, "value3")] = _value3;
    }

    function setString1(string _value1, string _value2, string _value3){
        values[sha3(valuesIndex, "value1")] = _value1;
        values[sha3(valuesIndex, "value2")] = _value2;
        values[sha3(valuesIndex, "value3")] = _value3;
        valuesIndex++;
    }

    function getString1() returns (string, string, string) {
        return (values[sha3(valuesIndex, "value1")],  values[sha3(valuesIndex, "value2")] , values[sha3(valuesIndex, "value3")] );
    }
}

// input "aaa", "bbb", "ccc"
// setString1
//    Transaction cost: 76308 gas.
//    Execution cost: 52924 gas.
// getString1
//    Transaction cost: 24753 gas.
//    Execution cost: 3481 gas.

contract gasTestMappingSha3Struct {

    struct value {
        string value1;
        string value2;
        string value3;
    }

    mapping(bytes32 => value) values;
    uint valuesIndex;

    function preSetString1(string _value1, string _value2, string _value3){
        values[sha3(valuesIndex)].value1 = _value1;
        values[sha3(valuesIndex)].value2 = _value2;
        values[sha3(valuesIndex)].value3 = _value3;
    }

    function setString1(string _value1, string _value2, string _value3){
        values[sha3(valuesIndex)].value1 = _value1;
        values[sha3(valuesIndex)].value2 = _value2;
        values[sha3(valuesIndex)].value3 = _value3;
        valuesIndex++;
    }

    function setString2(string _value1, string _value2, string _value3){
        values[sha3(valuesIndex)] = value({
            value1 : _value1,
            value2 : _value2,
            value3 : _value3
        });
        valuesIndex++;
    }

    function getString1() returns (string, string, string) {
        return (values[sha3(valuesIndex)].value1,  values[sha3(valuesIndex)].value2 , values[sha3(valuesIndex)].value3 );
    }

    function getString2() returns (string, string, string) {
        var v = values[sha3(valuesIndex)];
        return (v.value1,  v.value2, v.value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString1
//    Transaction cost: 106096 gas.
//    Execution cost: 82712 gas.
// setString2
//    Transaction cost: 105792 gas.
//    Execution cost: 82408 gas.
// getString1
//     Transaction cost: 24715 gas.
//     Execution cost: 3443 gas.
// getString2
//     Transaction cost: 24200 gas.
//     Execution cost: 2928 gas.
  • storage/memory/stack
pragma solidity ^0.4.11;

contract gasTestLocation {

    byte strg;

    function writeStorage(uint8 _value) {
        strg = byte(_value);
    }

    function writeMem(uint8 _value) {
        bytes memory mmry = new bytes(1);
        mmry[0] = byte(_value);
    }

    function writeStack(uint8 _value) {
        byte stck;
        stck = byte(_value);
    }

    function ReadStorage() returns (uint8) {
        return uint8(strg);
    }

    function writeReadStorage(uint8 _value) returns (uint8) {
        strg = byte(_value);
        return uint8(strg);
    }

    function writeReadMem(uint8 _value) returns (uint8) {
        bytes memory mmry = new bytes(1);
        mmry[0] = byte(_value);
        return uint8(mmry[0]);
    }

    function writeReadStack(uint8 _value) returns (uint8) {
        byte stck;
        stck = byte(_value);
        return uint8(stck);
    }

}

// input 1
// writeStorage
//    Transaction cost: 41788 gas.
//    Execution cost: 20324 gas.
// writeMem
//    Transaction cost: 21867 gas.
//    Execution cost: 403 gas.
// writeStack
//    Transaction cost: 21657 gas.
//    Execution cost: 193 gas.
// readStorage
//    Transaction cost: 21685 gas.
//    Execution cost: 413 gas.
// writeReadStorage
//    Transaction cost: 42008 gas.
//    Execution cost: 20544 gas.
// writeReadMem
//    Transaction cost: 22090 gas.
//    Execution cost: 626 gas.
// writeReadStack
//    Transaction cost: 21767 gas.
//    Execution cost: 303 gas.
  • ループ検証
pragma solidity ^0.4.11;

contract gasTestMemory {

    function mem10() {
        bytes memory v = new bytes(10);
    }

    function mem100() {
        bytes memory v = new bytes(100);
    }

    function mem1000() {
        bytes memory v = new bytes(1000);
    }

    function mem10000() {
        bytes memory v = new bytes(10000);
    }

    function mem100000() {
        bytes memory v = new bytes(100000);
    }

    function mem1000000() {
        bytes memory v = new bytes(1000000);
    }

    function mem10000000() {
        bytes memory v = new bytes(10000000);
    }

    function mem100000000() {
        bytes memory v = new bytes(100000000);
    }

    function init(bytes b, uint size) private {
        for (uint i = 0; i < size; i++) {
            b[i] = byte(0);
        }
    }

    function memInit10() {
        bytes memory v = new bytes(10);
        init(v, 10);
    }

    function memInit100() {
        bytes memory v = new bytes(100);
        init(v, 100);
    }

    function memInit1000() {
        bytes memory v = new bytes(1000);
        init(v, 1000);
    }

}

// mem10
//    Transaction cost: 21665 gas.
//    Execution cost: 393 gas.
// mem100
//    Transaction cost: 21555 gas.
//    Execution cost: 283 gas.
// mem1000
//    Transaction cost: 21599 gas.
//    Execution cost: 327 gas.
// mem10000
//    Transaction cost: 21687 gas.
//    Execution cost: 415 gas.
// mem100000
//    Transaction cost: 21577 gas.
//    Execution cost: 305 gas.
// mem1000000
//    Transaction cost: 21643 gas.
//    Execution cost: 371 gas.
// mem10000000
//    Transaction cost: 21621 gas.
//    Execution cost: 349 gas.
// mem100000000
//    Transaction cost: 21709 gas.
//    Execution cost: 437 gas.
// memInit10
//    Transaction cost: 23331 gas.
//    Execution cost: 2059 gas.
// memInit100
//    Transaction cost: 36796 gas.
//    Execution cost: 15524 gas.
// memInit1000
//    Transaction cost: 171970 gas.
//    Execution cost: 150698 gas.

stack検証

  • まとめ
    • スタックサイズで怒られた場合は関数を小分けにする。
    • modifierもスタックを消費する場合がある
      • この仕様が良くわからない。
pragma solidity ^0.4.11;

contract gasTestStack {

    function args(
        int _a1,
        int _a2,
        int _a3,
        int _a4,
        int _a5,
        int _a6,
        int _a7,
        int _a8,
        int _a9,
        int _a10,
        int _a11,
        int _a12,
        int _a13,
        int _a14,
        int _a15,
        int _a16,
        int _a17) {
        // error
    }

    function vars() {
        int _a1;
        int _a2;
        int _a3;
        int _a4;
        int _a5;
        int _a6;
        int _a7;
        int _a8;
        int _a9;
        int _a10;
        int _a11;
        int _a12;
        int _a13;
        int _a14;
        int _a15;
        int _a16;
        int _a17;
        // error
    }

    function rets() returns(
        int _a1,
        int _a2,
        int _a3,
        int _a4,
        int _a5,
        int _a6,
        int _a7,
        int _a8,
        int _a9,
        int _a10,
        int _a11,
        int _a12,
        int _a13,
        int _a14,
        int _a15,
        int _a16,
        int _a17) {
        // error
    }


    function argsRets(
        int _a1,
        int _a2,
        int _a3,
        int _a4,
        int _a5,
        int _a6,
        int _a7,
        int _a8
        ) returns(
        int,
        int,
        int,
        int,
        int,
        int,
        int,
        int) {
            var i = 1;
            // error
    }

    modifier m1(int _ma1) {
        int _l = 0;
        _;
    }

    function mod1(
        int _a1,
        int _a2,
        int _a3,
        int _a4,
        int _a5,
        int _a6,
        int _a7,
        int _a8
        ) m1(_a1) returns(
        int,
        int,
        int,
        int,
        int,
        int,
        int,
        int) {
            // OK
    }

    modifier m2(int _ma1) {
        int _v = 1;
        _;
    }

    function mod2(
        int _a1,
        int _a2,
        int _a3,
        int _a4,
        int _a5,
        int _a6,
        int _a7,
        int _a8
        ) m1(_a1) m2(_a2) returns(
        int,
        int,
        int,
        int,
        int,
        int,
        int,
        int) {
            // error
    }

    modifier m3(int _ma1) {
        int _i = 1;
        int _j = 1;
        _;
    }

    function mod3(
        int _a1,
        int _a2,
        int _a3,
        int _a4,
        int _a5,
        int _a6,
        int _a7,
        int _a8
        ) m3(_a1) returns(
        int,
        int,
        int,
        int,
        int,
        int,
        int,
        int) {
            // ok
    }
}
]]>
Fri, 04 Aug 2017 00:00:00 +0900
http://blog.potix.jp/2017/07/17/ethreum-contract-develop.html http://blog.potix.jp/2017/07/17/ethreum-contract-develop.html <![CDATA[MacOSでtruffleを使う]]> MacOSでtruffleを使う

サクサク開発したいときはこっちの方が便利かも。

nodejsをインストール

gethをダウンロード

tar xvf geth-darwin-amd64-1.6.7-ab5646c5.tar.gz
ln -s geth-darwin-amd64-1.6.7-ab5646c5 geth

testrpcのインストール

npm install -g ethereumjs-testrpc

truffleのインストール

npm install -g truffle

test rpcの起動

testrpc -b 1

プロジェクト作成

別のターミナルを開いて実行

mkdir project
cd project
truffle init webpack

コンパイル/テスト/デプロイ

truffle compile
truffle test
truffle deploy

webapp起動

さらに別のターミナルを開いて実行

cd project
npm run dev

プライベートネットにデプロイしたい場合

testrpcを止めてgethでプライベートネットを起動してtruffle deployすればOK

cd geth
vi private.json
{
  "config": {
      "chainId": 1,
      "homesteadBlock": 0,
      "eip155:Block": 0,
      "eip158Block": 0
  },
  "nonce": "0x0000000000000042",
  "timestamp": "0x00",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "extraData": "0x00",
  "gasLimit": "0x8000000",
  "difficulty": "0x01",
  "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "coinbase": "0x0000000000000000000000000000000000000000",
  "alloc": {}
}
./geth --datadir=./private init ./private.json
./geth --networkid "1" --nodiscover --ipcpath ${HOME}/.ethereum/geth.ipc  --rpc --rpcaddr 0.0.0.0 --rpcport 8545 -ws --wsaddr 0.0.0.0 --wsport 8546 --port 30303 --minerthreads 1 --rpccorsdomain "*" --datadir ./private console 2>> ./private.log

アカウントを作ってマイニングを初めて、getbalanceが0ではなくなるまで待ってアンロックする

> personal.newAccount("pass")
"0x65474bd903a10ff0954e5a86b4a28ce01fe86a80"
> miner.start()
> eth.getBalance("0x65474bd903a10ff0954e5a86b4a28ce01fe86a80")
90000000000000000000
> personal.unlockAccount("0x65474bd903a10ff0954e5a86b4a28ce01fe86a80")

この状態でtruffle deplayを行う

truffle deploy
Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  Migrations: 0xa4c0c8ea5aa6653e3f98bcdad65072428e0aa0fa
Saving successful migration to network...
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying ConvertLib...
  ConvertLib: 0x262845cb19de2d17c47e56d91b32bbde06f5ee22
  Linking ConvertLib to MetaCoin
  Deploying MetaCoin...
  MetaCoin: 0xc2b347b01cdb98fe7745aacb46e354dc4cad0b13
Saving successful migration to network...
Saving artifacts...

プライベートネットに、truffleを介してブラウザからアクセスする場合、 gethのオプションに–rpccorsdomain “*”をつけておかないとうまくいかないので注意。

]]>
Mon, 17 Jul 2017 00:00:00 +0900
http://blog.potix.jp/2017/07/13/ethreum-contract-develop.html http://blog.potix.jp/2017/07/13/ethreum-contract-develop.html <![CDATA[イーサリアムで遊ぶ]]> イーサリアムで遊ぶ

最近、ブロックチェーンにははまり中。 何か作りたくなったので電子チケットを売買するサービスをイーサリアムで作ろうと思ろうと思う。基本的には勉強が目的。 まぁ、いつかイーサリアムが廃れて別の分散アプリケーションプラットフォームが台頭してきたときは、いらない知識になるかもしれない。

開発環境を作る

何はともあれ、開発環境がないと始まらないので、作ってみる。 ちなみに、面倒くさかった。 今回はvertual boxにubuntu desktopをインストールして、そこに開発環境を構築した。 Mac環境はいろいろ汚れててはまりまくったからやめたっていうのは内緒。

ubuntu desktopをインストールする

以下からISOをダウンロードしてインストールする

パッケージを入れる

大体必要なものを入れておく

apt-get install -y curl git build-essential software-properties-common

goを入れる

1.8以降

sudo add-apt-repository -y  ppa:longsleep/golang-backports
sudo apt-get update
sudo apt-get install -y golang-go

gethを入れる

ここに従う

リポジトリを使っていれる。

sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get install -y ethereum solc

上をやっていれば基本的に必要ないけど、git cloneしたい人は以下

git clone https://github.com/ethereum/go-ethereum
cd go-ethereum
make geth

nodejsを入れる

ここに従う

sudo apt-get install -y nodejs npm
sudo npm cache clean
sudo npm install n -g
sudo n 6.11.1
sudo ln -sf /usr/local/bin/node /usr/bin/node
sudo apt-get purge -y nodejs npm

mistを入れる

ここに従う

curl https://install.meteor.com/ | sh
curl -o- -L https://yarnpkg.com/install.sh | bash

インストールが終わると、パスを通せとメッセージが出るので、yarnにパスを通す。

vi .profile
PATH="$HOME/bin:$HOME/.local/bin:$HOME/.yarn/bin:$PATH"

続き

source .profile
yarn global add electron@1.4.15
yarn global add gulp
git clone https://github.com/ethereum/mist.git
cd mist
yarn

remix (browser-solidity) を入れる

ここに従う

git clone https://github.com/ethereum/browser-solidity
cd browser-solidity
npm install
npm run prepublish

起動

ここまでで、必要なものが入ったので今度はこれらを起動していく。

mkdir work
cd work

gethを起動

ここまでで、必要なものが入ったので今度はこれらを起動していく。 前提として、開発用のプライベートなネットワークを作り、そこに作った契約を配置する。 そのため、gethの初期化処理に渡す、genesisファイルを作る必要がある。

vi testGenesis.json
{
  "config": {
      "chainId": 1,
      "homesteadBlock": 0,
      "eip155Block": 0,
      "eip158Block": 0
  },
  "nonce": "0x0000000000000042",
  "timestamp": "0x00",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "extraData": "0x00",
  "gasLimit": "0x8000000",
  "difficulty": "0x01",
  "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "coinbase": "0x0000000000000000000000000000000000000000",
  "alloc": {}
}

次にこのファイルを指定して初期化する

geth --datadir=$(pwd)/data init $(pwd)/testGenesis.json

gethの起動

geth --networkid "1" --nodiscover --ipcpath ${HOME}/.ethereum/geth.ipc  --rpc --rpcaddr 0.0.0.0 --rpcport 8545 -ws --wsaddr 0.0.0.0 --wsport 8546 --port 30303 --minerthreads 1 --datadir $(pwd)/data console 2>> $(pwd)/geth.log

mistを起動する

別のターミナルを開く

cd mist/interface
meteor --no-release-check

さらに別のターミナルを開く

cd mist
electron .

mistが起動する

remixを起動

さらに別のターミナルを開く

cd browser-solidity
npm start

firefoxでhttp://localhost:8080にアクセスする

結果

最終的にこんな画面が出来上がる

../../../_images/dev.png
]]>
Thu, 13 Jul 2017 00:00:00 +0900
http://blog.potix.jp/2017/05/20/rancher.html http://blog.potix.jp/2017/05/20/rancher.html <![CDATA[rancherの冗長化について]]> rancherの冗長化について

rancherにコンテナを立ち上げる前に。 今回の環境でのrancherの冗長化戦略について考えてみる。

外部アクセスに対する冗長性

まず、外部からの接続に対してその接続先が健全なノードであることを保証できなければ、サービス提供もままならないので、この問題を考えてみる。 前提として、外部からアクセスが来るということは、DNSで名前を引いて、そのIPアドレスにアクセスしてくるということを指すと思うのでこれを前提に考えると、 大きく2つの方法があると思う。

    1. floating ip(vrrp等)使って、どこかに故障があったとしてもDNSで引けたそのアドレスは大丈夫にする。
    1. そもそも、故障があったノードのIPアドレスはDNSで引けなくする。 (DNSによるfailover)

結論から言ってしまえばどちらも可能。DNSに関してはrancherのヘルスチェックとの連携でいい感じにしてくれるけど、ちょっとした罠がある。

1のパターンを考えてみる。

    1. dockerの管理外にkeepalivedを立ててvrrpする。-> もう何もかもrancherの外。インフラとして事前に設定しておく必要がある。
    1. keepalivedが起動するコンテナをrancherで管理する。 -> コンテナだけはrancher管理。networkingにはhostを指定する必要ある。

2のパターンを考えてみる。

    1. 障害検出機能とDNSを連動させる。-> rancherのヘルスチェック情報をDNSに連携するものがrancherのカタログにある。
    1. awsのroute 53とか使う。 -> もう何もかもrancherの外。お金もかかる。

アドレスがたくさんある人やグローバルが同じサブネットにある人は1ができると思う。 今回の環境だと、さくらVPSでvrrpのような冗長化はサブネットも違うためやりづらい。 2のパターンのほうが無難。2-bはお金がかかるので躊躇。

2-aを考えた場合、ちょっとした罠がある。 rancherのヘルスチェック情報とDNSの連携をさせる機能はrancherのネットワーク内で完結するため、途中の経路のネットワークの健全性をみれていない。 つまり、途中のFWやネットワークが不安定だったりした場合は一切検出できない。また、プロセスが腐ってる場合もちゃんと監視していないと検出できないから注意が必要。

本来は、外から監視て、ちゃんとサービス提供がなされていることを確認すべきだと思う。 というわけで、まだ完成してないけど、外部から監視して、PowerDNSのレコードを更新するものを作ってる。 最終的にrancherカタログにするつもり。

ノードの停止に対する冗長性

ホストが死んだら、そのホストのしごとを別のホストが引き継いでくれる必要がある。 rancherはちゃんと死んだホストにいたコンテを別のホストに動かしてくれる。 ラベル等で特定のホストにバインドしてる場合は無理。

しかし、ここには罠がある。 実は、rancher serverがその管理をしているようで、rancher serverが死んでいた場合。別のホストへの移動が発生しない。 なので、rancher serverは実は結構重要で、冗長化しておくことをお勧めする。

本来、rancher serverが健全性の維持の役目を担うのではなく、健全性の維持は環境内で完結するべきだと思う。 この辺りは、今後のrancherのバージョンアップに期待。

ストレージの冗長化

ストレージに関しては、ホストのディスクをそのまま使うと、ディスクやホストが死んだときに困るので単純に使うのはやめたほうがいい。 sakura vpsであることを考えると、ここは積極的に分散ストレージを使うのがよさそうなのでglusterfsを使うことにした。 glusterfsならnfsをマウントするより、fuseクライアントでマウントして、マウントした先をdockerに見せてあげれば、耐障害性的にいいと想う。

]]>
Sat, 20 May 2017 00:00:00 +0900
http://blog.potix.jp/2017/05/04/rancher.html http://blog.potix.jp/2017/05/04/rancher.html <![CDATA[rancherというものを使ってみる]]> rancherというものを使ってみる

前置き

自宅のGiga HUBがちょっと前に壊れて、100M HUBで代用してたけど、今度はブロードバンドルータが壊れた。 なので、ブロードバンドルータ買いに行こうと思ったけど、ネットワーク構成をよくよく眺めてみると、 過去に作ったESXi環境のgatewayにしてたSEIL/x86を、そのままブロードバンドルータ変わりにすればいい気がしてきた。

さらに、ESXi環境はvcenter serverがなくて、管理が個別のESXiに繋がないといけなくてだるかったけど、 dockerとrancherなら、ぽこぽこコンテナ上げられるみたいなことを会社の隣の席の人が言ってたから、 いい感じになるかもしれないと思って、安直に乗っかってみた。

始める前に

以下のサポートバージョンのdockerが入ったlinuxならよさげ、 RancherOS, ubuntu, CentOS 7 がよさげ

調べた時点では、

  • CentOS -> 1.12.6
  • ubuntu -> 17.03

RancherOS amd64なら17.03がデフォルトっぽい。自由に切り替えれるっぽいけど。

たぶん、defaultが不安定ということはないだろうから、ubuntuがベストか?

RancherOS使えよとかいわれそうだけど、よくわかってないからやめといた。 RancherOSはinitがシステムコンテナでその上でユーザコンテナとか書いてあった。

隣の席の人がRancherOSいいよ!って言ったら使ってみる。

構成

../../../_images/rancher.png

グローバルアドレスは伏せてます。

ubuntu Aはいつもの開発サーバなので、OS変えたりはしない。ubuntu B,CがESXiだったやつ。Dは成り行きで(間違って)増えた。

コンテナ通信用にローカルセグメント172.16.60.0/24を用意してみたけど、正直使うかどうか謎。

全てのノードにアドレスを振っているのはESXiと違って、rancherはoverlay networkを構築して必要に応じてポートを出すだけだから。

さくらVPSもrancherで管理する。ノリ的にはさくらVPSが本番環境、自宅が開発環境。趣味なのでステージングとかはない。 rancherのenvironmentsで環境ごとにホストの登録ができる。また、managed network(overlay network)も環境ごとに作られるとドキュメントにある。

あと、今回はちょっとチャレンジをしていて、 “自宅環境はグローバルにoverlay network作ると、172.16.51.0/24側の物理結線したネットワークもったいなくね?将来ローカルを10Gにして、 分散ファイルシステムでトラフィックたくさん流すかもしれないよ!” という天のお告げが降ってきたの、さくらVPSと自宅環境どちらもローカル側にoverlay networkを作ってみます。

たぶん、rancherは素直に、どちらもglobalにoverlayネットワーク作ることを想定していると思う。敢えて想定外のことをやってみる。 ちなみに、コンテナが外からのアクセスを受けるときはiptablesのPREROUTINGでDNATするので基本ポートが合えば何でも食べれるので問題ないと思う。 アドレス指定したらその限りじゃないけど。。。

尚、今回の作業はすべてPCからやっています。

esxiを消してubuntuにする

ESXiさようなら。ubuntuこんにちは。

ネットワーク構成変更

頑張る

dockerに関連する設定

dockerを使うのでip_forwardは1にしておく必要がある。

# vi /etc/sysctl.conf
.
.
net.ipv4.ip_forward=1
.
.
# sysctl -p

ubuntu Aでdockerインストール

ドキュメント

リポジトリから入れてみる

# sudo apt-get install \
>     apt-transport-https \
>     ca-certificates \
>     curl \
>     software-properties-common

# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# sudo apt-key fingerprint 0EBFCD88

#  sudo add-apt-repository \
>    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
>    $(lsb_release -cs) \
>    stable"

# sudo apt-get update

# sudo apt-get install docker-ce

rancher serverを起動

ドキュメント

listenポート番号は10080にしてみました。

とりあえず、rancher serverを立てるとWEBでアクセスできるようになる。 そして、WEBからホスト追加しようとすると、各ホストでこのコマンドうてみたいなのを教えてくれる。 そのコマンドを実行すると、rancher agentがrancher serverに登録しにいく。

グローバルから入ってくる場合はSSLを終端するために、nginxで20080にアクセスしたものを10080にリバースプロキシするようにした。 オレオレ証明書だとうまくいかなかったのでlet’s encryptで証明書を取得した。

enginxの設定はこんな感じ。これはドキュメントのまんま。 https://docs.rancher.com/rancher/v1.5/en/installing-rancher/installing-server/basic-ssl-config/

upstream rancher {
        server X.X.X.D:10080;
}

server {
        listen       X.X.X.D:20080 ssl spdy;
        server_name  hogehoge.potix.jp;

        ssl_certificate      /etc/letsencrypt/live/hogehoge.potix.jp/fullchain.pem;
        ssl_certificate_key  /etc/letsencrypt/live/hogehoge.potix.jp/privkey.pem;

        location / {
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Forwarded-Port $server_port;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_pass http://rancher;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "Upgrade";
                proxy_read_timeout 900s;
        }
}

以下のコマンドをubuntu Aで実行。

# docker run -d --restart=unless-stopped -p X.X.X.D:10080:8080 -p 172.16.50.20:10080:8080 -p 172.31.1.130:10080:8080 --name rancher rancher/server

X.X.X.D:10080:8080 => globalからアクセスする先
172.16.50.20:10080:8080 => PC側からアクセスする先
172.31.1.130:10080:8080 => リモート管理側からアクセスする先

dockerの中見たいときは以下で

# docker exec  -it rancher  /bin/bash

これで、PCからブラウザでhttp://172.16.50.20:10080 にアクセスすれば、rancherの画面が見える。

../../../_images/disp1.png

Access Controll

Admin -> Access Control で ローカルを選んでユーザを登録する

defaultのenrironmentに自宅環境のhost追加

管理ホストを追加してみる。

infrastracture -> hosts -> Add Host

Host Registration URLの設定画面だ出てくる(最初の1回だけ、以後AdminのSettingから変更可)。 rancher agentがrancher serverにアクセスするURLを指定するらしい。

something else を選択して、https://hogehoge.potix.jp:20080 を入れてあげる。

どうもこのパラメータは環境ごとに切り替えられないようで、全部の環境で同じになるようにしてあげないと面倒。

また、このURLに対して定期的に通信が発生しているみたい(たぶんヘルスチェック?)。 そういう意味でも、グローバルを指定しておくのは正しいかも。 グローバルで制御不能 = サービスダウン的なノリ。

この後、Add Hostの画面になる。

Make sure any security groups or firewalls allow traffic:
From and To all other hosts on UDP ports 500 and 4500 (for IPsec networking)

とかいっていたので、overlay networkがルータやFWを超える場合はポートを開けてあげよう。 agentになるホストに関しては特に何もしなくても初期状態なら問題なさそう。IPSECも結局DNATで処理される。

ラベルは key = develop.host.no, value = 1 と入れておいた。 このラベルは割と重要で、スケジューラー設定で特定のホストに対して任意のコンテナを起動させたい場合に、このラベルと一致するかどうかでそのホストかどうか判定ができる。 ホストだけじゃなく、コンテナやサービスのラベルを使っても制御ができる。

次に、なんかよくわからないことを言われた。

Specify the public IP that should be registered for this host. If left empty, Rancher will auto-detect the IP to use. This generally works for machines with unique public IPs, but will not work if the machine is behind a firewall/NAT or if it is the same machine that is running the rancher/server container.

ドキュメントにはこうある

By default, the IP of a VM with a private IP and public IP will be set to match the IP specified in the registration URL. For example, if a private IP is used in the registration URL, then the host’s private IP will be used. If you wanted to change the host’s IP address, you’ll need to edit the command provided from the UI. In order for the Rancher agent container to be launched correctly, set the CATTLE_AGENT_IP environment variable to the desired IP address. All the hosts within Rancher will need to be on the same network as Rancher server.

説明がよくわからないんだけど。。。

軽く試した結果。ここに指定したアドレス側でoverlay networkを構築するみたい。 指定しないとregistration URLアドレスからアドレスを推測してoverlay networkが作られるだけだと思う。

というわけで、これを検証する意味も込めて、ここは敢えてpublic ipにprivateなアドレス172.16.51.1を入れる。 こうすると172.16.51.0/24側でoverlay networkを構築するはず。

コピペしてエージェント起動してみる。

# sudo docker run -e CATTLE_AGENT_IP="172.16.51.1"  -e CATTLE_HOST_LABELS='develop.host.no=1'  --rm --privileged -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/rancher:/var/lib/rancher rancher/agent:v1.2.2 https://hogehoge.potix.jp:20080/v1/scripts/7FC97CCF075415608954:1483142400000:XcEE4ZCaeJ6A1XcQrD5ojzhmx8
 INFO: Running Agent Registration Process, CATTLE_URL=https://hogehoge.potix.jp:20080/v1
 INFO: Attempting to connect to: https://hogehgoe.potix.jp:20080/v1
 INFO: https://hogehoge.potix.jp:20080/v1 is accessible
 INFO: Inspecting host capabilities
 INFO: Boot2Docker: false
 INFO: Host writable: true
 INFO: Token: xxxxxxxx
 INFO: Running registration
 INFO: Printing Environment
 INFO: ENV: CATTLE_ACCESS_KEY=5EBB8DC739BF413EACCF
 INFO: ENV: CATTLE_AGENT_IP=172.16.51.1
 INFO: ENV: CATTLE_HOME=/var/lib/cattle
 INFO: ENV: CATTLE_HOST_LABELS=develop.host.no=1
 INFO: ENV: CATTLE_HOME=/var/lib/cattle
 INFO: ENV: CATTLE_HOST_LABELS=develop.host.no=1
 INFO: ENV: CATTLE_REGISTRATION_ACCESS_KEY=registrationToken
 INFO: ENV: CATTLE_REGISTRATION_SECRET_KEY=xxxxxxx
 INFO: ENV: CATTLE_SECRET_KEY=xxxxxxx
 INFO: ENV: CATTLE_URL=https://hogehgoe.potix.jp:20080/v1
 INFO: ENV: DETECTED_CATTLE_AGENT_IP=X.X.X.A
 INFO: ENV: RANCHER_AGENT_IMAGE=rancher/agent:v1.2.2
 INFO: Launched Rancher Agent: ebbe896d3b285a1d4a03a318113bb130af5b00fc388ba85d43d84db829ccc932

いけったっぽい。ubuntu C,Dも同じように登録してあげる。

../../../_images/disp2.png

enrironmentにproduction追加

manage environment -> add environment

cattleを選択して新規の環境を作る

productionのenrironmentにさくらVPSのhost追加

infrastracture -> hosts -> Add Host

ラベルは key = service.host.no, value = 1 と入れておいた。

public IPの欄は172.16.200.1を入れる。これで、172.16.200.0/24側にoverlay networkを構築するはず。

表示されたコマンドを実行して、hostを登録する。

F,Gも同じように登録してあげる。

../../../_images/disp3.png

overlay network確認

本当にローカル側にoverlay networkが構築されているか。 nat traversalのパケットをみてみる。 自宅環境側。

# ip addr show eno1
3: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 80:ee:73:ad:61:55 brd ff:ff:ff:ff:ff:ff
    inet 172.16.51.1/24 brd 172.16.51.255 scope global eno1
       valid_lft forever preferred_lft forever
    inet6 fe80::82ee:73ff:fead:6155/64 scope link
       valid_lft forever preferred_lft forever

# tcpdump -i eno1 -vvv -n -s 9999  port 4500 -c 5
tcpdump: listening on eno1, link-type EN10MB (Ethernet), capture size 9999 bytes
04:45:41.635501 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto UDP (17), length 124)
    172.16.51.1.4500 > 172.16.51.3.4500: [no cksum] UDP-encap: ESP(spi=0xc878fba4,seq=0x18f76), length 96
04:45:41.636227 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto UDP (17), length 124)
    172.16.51.3.4500 > 172.16.51.1.4500: [no cksum] UDP-encap: ESP(spi=0xc2562367,seq=0x11ab7), length 96
04:45:41.636379 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto UDP (17), length 116)
    172.16.51.1.4500 > 172.16.51.3.4500: [no cksum] UDP-encap: ESP(spi=0xc878fba4,seq=0x18f77), length 88
04:45:42.452258 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto UDP (17), length 124)
    172.16.51.1.4500 > 172.16.51.2.4500: [no cksum] UDP-encap: ESP(spi=0xcca3a348,seq=0x1894d), length 96
04:45:42.452535 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto UDP (17), length 124)
    172.16.51.2.4500 > 172.16.51.1.4500: [no cksum] UDP-encap: ESP(spi=0xc7b13622,seq=0x11ce1), length 96

ちゃんと流れてる。

さくらVPS側も見てみる。

# ip addr show ens4
3: ens4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 9c:a3:ba:03:a0:9e brd ff:ff:ff:ff:ff:ff
    inet 172.16.200.1/24 brd 172.16.200.255 scope global ens4
       valid_lft forever preferred_lft forever
    inet6 fe80::9ea3:baff:fe03:a09e/64 scope link
       valid_lft forever preferred_lft forever

# tcpdump -i ens4 -vvv -n -s 9999  port 4500 -c 5
tcpdump: listening on ens4, link-type EN10MB (Ethernet), capture size 9999 bytes
04:50:22.984222 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto UDP (17), length 124)
    172.16.200.1.4500 > 172.16.200.2.4500: [no cksum] UDP-encap: ESP(spi=0xc8d5839f,seq=0x1277), length 96
04:50:22.984837 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto UDP (17), length 124)
    172.16.200.2.4500 > 172.16.200.1.4500: [no cksum] UDP-encap: ESP(spi=0xcdb79709,seq=0x93c), length 96
04:50:22.985150 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto UDP (17), length 116)
    172.16.200.1.4500 > 172.16.200.2.4500: [no cksum] UDP-encap: ESP(spi=0xc8d5839f,seq=0x1278), length 88
04:50:24.115459 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto UDP (17), length 124)
    172.16.200.1.4500 > 172.16.200.3.4500: [no cksum] UDP-encap: ESP(spi=0xc8ae6fbe,seq=0xfa7), length 96
04:50:24.116064 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto UDP (17), length 124)
    172.16.200.3.4500 > 172.16.200.1.4500: [no cksum] UDP-encap: ESP(spi=0xc14f34e4,seq=0x7d4), length 96

ちゃんと流れていることがわかる。

ネットワークの流れを、 以下のコマンドを駆使して調べると、

  • iptables -t nat -L -v -n
  • iptables -L -v -n
  • ip addr
  • ip route list
  • ip route get
  • brctl show
  • docker inspect
  • docker exec
  • docker network inspect
  • netstat -an
  • lsof
  • tcpdump

細かいところは、突っ込みあると思うけど、ざっくりこんなイメージ

../../../_images/guess.png

大雑把に言うと、各ホストで外から入ってきたのはDNATで食べてコンテナに流してあげる。 コンテナ発のパケットは各ホストのPOSTROUTINGのMASQUERADEで出ていく。

加えて、今回rancher serverでSSL終端して裏に流した関係で、DNATに食われなくて、docker-proxyが食べるルートがある。

この基本的なdockerのブリッジネットワーク上のコンテナに10.42.X.Xのアドレスを割り当てて、ipsecルータを作ってipsecでVPNを構築している。 なので、10.42.X.Xはoverlay上のホストということになり、10.42.X.X間はホストを超えて通信が可能になっている。

とは言っても、ホストを超えて通信できるようになったけど、相変わらずポートを外出しするところに変わりはない。

あと、IPSECって遅そうだから。AES-NI必須な気がしてならない。 気になったので参考程度にやってみた。

AES-NIのあるマシン

Model name:            Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz

# 暗号化しない
# dd if=/dev/zero count=100 bs=10M | cat > /dev/null
100+0 records in
100+0 records out
1048576000 bytes (1.0 GB, 1000 MiB) copied, 0.509076 s, 2.1 GB/s

# 暗号化する
# dd if=/dev/zero count=100 bs=10M | openssl enc -e -aes-256-cbc -pass pass:foobar -out /dev/null
100+0 records in
100+0 records out
1048576000 bytes (1.0 GB, 1000 MiB) copied, 3.03892 s, 345 MB/s

大体6倍ぐらい時間かかる。

AES-NIのないやつ

Model name:            Intel(R) Atom(TM) CPU D525   @ 1.80GHz

# 暗号化しない
# dd if=/dev/zero count=100 bs=10M | cat > /dev/null
100+0 records in
100+0 records out
1048576000 bytes (1.0 GB, 1000 MiB) copied, 2.72433 s, 385 MB/s

# 暗号化する
# dd if=/dev/zero count=100 bs=10M | openssl enc -e -aes-256-cbc -pass pass:foobar -out /dev/null
100+0 records in
100+0 records out
1048576000 bytes (1.0 GB, 1000 MiB) copied, 52.6625 s, 19.9 MB/s

大体20倍近い時間がかかる。

今回のこの環境、自宅とさくらVPSでenvironment分離したし、ローカル側にoverlay network作ったから、IPSECとかじゃなくて、GREとかIPIPでよかったんじゃ。。。 あ、でもIPだと何かと問題ありそうだから、IPSECしないL2TPv2とかL2TPv3とか。。。

今回はここまで。次回はrancherでコンテナを作るところから。

]]>
Thu, 04 May 2017 00:00:00 +0900
http://blog.potix.jp/2016/05/12/airbone_drone.html http://blog.potix.jp/2016/05/12/airbone_drone.html <![CDATA[raspberry pi3からgobotとpaypal/gatt使ってairborn droneを操作する]]> raspberry pi3からgobotとpaypal/gatt使ってairborn droneを操作する

前置き

raspberry pi 3買ったので、無線LANやbluetooth使ってなんかしたい。 goの勉強がしたい。 というわけで、gobotにairborn droneのpaltformを追加してdroneを操作してみた。

やったこと

  1. raspberry pi 3にubuntu mateインストールする
  2. goをインストールする
  3. gobotを落として、gobotのble-wipブランチを参考に新たにairbone_droneのプラットフォームを追加
  4. その際、paypal/gatt使うとパニックするので、bleについて調べて、paypal/gattをforkして動くようにする
  5. rolling-spiderのコードを参考にしつつ、parrotのSDKを参考にしつつ、freeflight3の通信パケットをwiresharkで眺めつつ、解析しながらコードを書く

たった5ステップでdroneを操作することができたよ

今後

とりあえず、最初の一歩は超えられたので次は音声で操作できるようにしする予定。

]]>
Thu, 12 May 2016 00:00:00 +0900
http://blog.potix.jp/2016/05/12/esxi_install.html http://blog.potix.jp/2016/05/12/esxi_install.html <![CDATA[ds57u5にESXiを入れる]]> ds57u5にESXiを入れる

blogに書き忘れた1年前ぐらいのメモがあったのでいまさらだけど載せておく

前置き

esxiのカスタムイメージ作り方

  1. vsphere powecli 6.0をインストールする

 2. ESXi-Customizer-PSをダウンロードする

  1. コマンドプロンプトを管理者権限で実行
  • 以下のコマンドを実行
> powershell
> c:\vsphere\ESXi-Customizer-PS-v2.4.ps1 -v60 -vft -load sata-xahci

esxiのインストール

  1. カスタムイメージをCDに焼く
  2. CDからブートして、ガイドに従ってインストール
  3. 再起動すれば、晴れてesxiが起動する

起動後の設定

  • マネージメントネットワーク設定
  • sshを有効にする
    • トラブルシューティングオプションからsshを有効にする

patch適用

  1. esxcli network firewall ruleset set -e true -r httpClient
  2. esxcli software profile update -d https://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml -p ESXi-6.0.0-20160302001-standard
  3. reboot
  4. esxcli software vib update -n net-e1000e -d https://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml
  5. reboot
]]>
Thu, 12 May 2016 00:00:00 +0900
http://blog.potix.jp/2013/05/04/sqlite_udf.html http://blog.potix.jp/2013/05/04/sqlite_udf.html <![CDATA[sqliteのudfをつくってみた]]> sqliteのudfをつくってみた

json文字列から特定のデータを検索するsqliteのudfを作ってみた。 jsonをカラムに突っ込んじゃったけど、jsonの特定の値で何かしたくなった場合に使えるかもしれない。 まぁ、需要はあまりないよね。

スクラッチから書く気力は無かったのでvjsonのコードをベースにして作ってみた。

基本的な動作しか確認していないので、エスケープ処理とかUTF-8とかの処理のテストがまだ不十分。 あとコンパイルオプションで変更可能だけど、ネストしたキーの最大長が1024バイト以上あるjson文字列は扱えなかったり。 32回以上ネストしているjson文字列は扱えなかったりする。

使い方

insertするときにjfminでラップするとjson文字列の余分なスペースとかを取り除いてくれる。

挿入の例:
  select load_extension('../sqlite_json_finder.so.0.0.0');  <--- これでライブラリをロードする
  create table data (key integer, value text);"
  insert into data values (1, jfmin('{ "a" : 1, "b" : { "c" : 1 } }'));

selectでjfgetを使うとjson文字列の中から特定の値を取ってくる。

データの検索例:
  select key,jfget(value, 'a') frm data;

ネストの検索も’.’でつなげば可能。

ネストしたデータの検索例:
  select key,jfget(value, 'b.c') frm data;

速度

検索速度に関しては、ネストしてないものなら割と速いと思う。比較対象探し中。 ちなみに、jsonをbsonに変換してblobで突っ込んでbsonを検索するバージョン作ってみたけどbson版のほうが速かった。 さすがbson。ただしblobにすると”select * “ができないという問題がある。あとbsonはjsonよりデータサイズ増えたりする。なのでやめました。 あと、根本的な問題としてindexはれないので、スキーマ切ってnativeなsql叩くよりは全然遅いよ。手元のデータでは3倍強ぐらい遅かった。 まぁ、でも速度をあまり要求されないところで使う分には十分な性能だと思う。

TODO

たくさんあります。
  • ネストの検索に無駄があるので改善する
  • keyに”.”がある場合はエスケープできるようにする
  • エスケープ処理のテスト
  • UTF-8周りのテスト
  • ネスト回数制約をなくす
  • keyの長さ制約をなくす
  • mysqlで利用できるようにする

その他

実は既にjsonをパースするudfのmysql版はあったりする。mysql対応したら比較してみるつもり。
]]>
Sat, 04 May 2013 00:00:00 +0900
http://blog.potix.jp/2013/03/10/ejdb.html http://blog.potix.jp/2013/03/10/ejdb.html <![CDATA[ejdb]]> ejdb

ejdbを使ってみたので書き留めておく。

ejdbって?

ejdbはmongodbのsqliteみたいなもの。 server, clientではなくファイルにデータを保存する。 mongodbと同じデータ形式を扱えるので、jsonデータをそのまま保存できる。 クエリーもmongodbと同じようなものが用意されている。

中身はtokyo cabinetのコードをベースにしたCのライブラリになっていて、 中でmongodbのmongo-c-driverを使っているみたい。 それにnodejsとpythonのbindingをしている。 ちなみに、githubのissue眺めてるとkyoto cabinetのコードを使った方が良くない?的な質門に、 kyoto cabinetはGPLでtokyo cabinetはLGPLだからこっち選んだ的なことが書いてあった。

また、現状ではnode.jsだと0.8.0以上、pythonだと3.2以上で動作する。

とりあえず、使ってみる

普通にmakeとかやってもうまく行かなかったので、 以下のようにインストール

  • tcejdbのインストール
$ cd tcejdb
$ ./configure --prefix=/usr/local
$ make
$ sudo make install
$ cd ..
  • pyejdbインストール (python3のヘッダ必須)
$ cd pyejdb
$ python3 setup.py build
$ sudo python3 setup.py install
$ cd ..
  • node.jsモジュール
$ sudo npm install -g node-gyp
$ npm build .
(build/Releaseにできてる)

とりあえず、ビルドとインストールが一通り終わったところで、 pythonのサンプルを改造してあまり意味はないかも知れないけど、 ejdbとsqliteをシンプルな検索で性能比較してみた。

  • ejdb テストコード (ejdb.py)
import pyejdb
import datetime
import os

if not os.path.exists("zoo"):
    ejdb = pyejdb.EJDB("zoo", pyejdb.DEFAULT_OPEN_MODE | pyejdb.JBOTRUNC)
    ejdb.ensureCollection("parrots2")
    ejdb.ensureStringIndex("parrots2", "extra1")
    ary = []
    for i in range(1, 100000):
        ary.append({
            "name": "Grenny" + str(i),
            "type": "African Grey",
            "male": True,
            "age": 100,
            "likes": ["green color", "night", "toys"],
            "extra1": "hogehoge" + str(i),
            "extra2": "abc"
        })
    ejdb.save("parrots2", *ary)
    ejdb.close()

start = datetime.datetime.now()
ejdb = pyejdb.EJDB("zoo", pyejdb.DEFAULT_OPEN_MODE)
for i in range(1, 10000):
    with ejdb.find("parrots2", {"extra1" : "hogehoge%d" % (i) }) as cur:
        for p in cur:
            #print(p["name"])
            pass
ejdb.close()
end = datetime.datetime.now()
print(str(end-start))
  • sqlite テストコード (sqlite.py)
import sqlite3
import datetime
import os

if not os.path.exists("data.db"):
    con = sqlite3.connect("data.db")
    sql = """
    create table test (
      name varchar(32),
      type varchar(32),
      age integer,
      male integer,
      likes varchar(64),
      extra1 varchar(32),
      extra2 varchar(32)
    );
    """
    con.execute(sql)
    sql = "create INDEX extra1index on test(extra1);"
    con.execute(sql)
    for i in range(1, 100000):
        sql = "insert into test values ('Grenny%d', 'African Grey', 1, 100, 'green color, night, toys', 'hogehoge%d', 'abc');" % (i,i)
        con.execute(sql)
    con.commit()
    con.close()

start = datetime.datetime.now()
con = sqlite3.connect("data.db")
c = con.cursor()
for i in range(1, 10000):
    c.execute("select * from test where extra1 == 'hogehoge%d';" % (i))
    for row in c:
        #print(row[0])
        pass
con.close()
end = datetime.datetime.now()
print(str(end-start))
  • 実行結果
$ python3 ejdb.py
0:00:02.397433
$ python3 sqlite.py
0:00:00.408522

まぁ、予想通りだけど、sqliteの方が6倍ぐらい速かった。 こういうのは、格納するデータの特性に応じて使い分けるのが良いと思う。

あと、ejdbは複雑な検索にはindexが効かなくなるようで、 $strorとか使うとindexが意味をなしてなかった。

ちなみに、lsをやると

$ ls -al

合計 35648
drwxrwxr-x  2 user user     4096  3月 10 04:56 .
drwxrwxr-x 12 user user     4096  2月 17 16:13 ..
-rw-r--r--  1 user user 10903552  3月 10 04:56 data.db
-rw-rw-r--  1 user user      926  3月 10 04:56 ejdb.py
-rw-rw-r--  1 user user      972  3月 10 04:53 sqlite.py
-rw-r--r--  1 user user   529008  3月 10 06:14 zoo
-rw-r--r--  1 user user 21702184  3月 10 06:14 zoo_parrots2
-rw-r--r--  1 user user  3344896  3月 10 06:14 zoo_parrots2.idx.sextra1.lex

結果から分かるように、データベース名、コレクション名でファイルが出来ていて、 1つのindexに対して、ファイルが1つ出来る仕様になってるみたい。 容量もsqliteより大きめ。

補足

ejdbのCLIツールも用意されてるようで、以下のようにすればさっき作ったファイルをCLIで操作できる。

$ cd pyejdb
$ cd samples
$ node ../../node/bin/cli.js
Welcome to EJDB CLI v1.0.63
ejdb> db.open("zoo")
{ file: '/home/hiro/Download/Softmotions-ejdb-3fd8658-2/pyejdb/samples/zoo',
  collections:
   [ { name: 'parrots2',
       file: '/home/hiro/Download/Softmotions-ejdb-3fd8658-2/pyejdb/samples/zoo_parrots2',
       records: 99999,
       options:
        { buckets: 131071,
          cachedrecords: 0,
          large: false,
          compressed: false },
       indexes:
        [ { field: 'extra1',
            iname: 'sextra1',
            type: 'lexical',
            records: 99999,
            file: '/home/hiro/Download/Softmotions-ejdb-3fd8658-2/pyejdb/samples/zoo_parrots2.idx.sextra1.lex' } ] } ] }
ejdb> db
.
.
.

追記

ejdbはquery objectはずして, sqliteはwhereを外して、99999件全部取るだけにして比較したらejdb半端なく遅かった。

$ python3 sqlite.py
0:00:00.386605
0:00:00.414767
$ python3 ejdb.py
0:00:02.387387
0:00:30.644258

でも、$fieldsで取得するフィールドを絞ると速くなるから、中のデータ変換処理か何かかが遅いのかも

$ python3 ejdb.py
0:00:02.407641
select all field
0:00:31.256630
select name field only
0:00:08.942136
]]>
Sun, 10 Mar 2013 00:00:00 +0900
http://blog.potix.jp/2013/01/05/lazy_evaluation_by_c.html http://blog.potix.jp/2013/01/05/lazy_evaluation_by_c.html <![CDATA[C言語で遅延評価]]> C言語で遅延評価

今更だけど、C言語で遅延評価やろうと思ったらこんな感じに、

関数ポインタ使ってやればいいだけのような気がしてならない。

#include <stdio.h>
#include <limits.h>

#define LAZY_DEF(type, funcname, ...) \
  typedef type (*LAZY_##funcname)(__VA_ARGS__); \
  type lazy_##funcname(__VA_ARGS__)
#define LAZY_ARG(funcname, callname, ...) \
  LAZY_##funcname callname, __VA_ARGS__
#define LAZY_FUNC(funcname, ...) \
  lazy_##funcname, __VA_ARGS__

LAZY_DEF(int, sub, int suba, int subb)
{
        return suba - subb;
}

LAZY_DEF(int, div, int diva, int divb)
{
        return diva / divb;
}

int
hoge(LAZY_ARG(sub, sub1, int sub1a, int sub1b), LAZY_ARG(div, div1, int diva, LAZY_ARG(sub, sub2, int sub2a, int sub2b)))
{
        if (sub1(sub1a, sub1b) != 0)
                return div1(diva, sub2(sub2a, sub2b));
        else
                return INT_MAX;
}

int main(void) {
        int a, b, c;

        a = 10;
        b = 3;
        c = 3;
        printf("%d\n", hoge(LAZY_FUNC(sub, b, c), LAZY_FUNC(div, a, LAZY_FUNC(sub, b, c))));

        a = 10;
        b = 4;
        c = 2;
        printf("%d\n", hoge(LAZY_FUNC(sub, b, c), LAZY_FUNC(div, a, LAZY_FUNC(sub, b, c))));

        return 0;
}
]]>
Sat, 05 Jan 2013 00:00:00 +0900
http://blog.potix.jp/2012/10/22/s3fs_vs_s3fuse.html http://blog.potix.jp/2012/10/22/s3fs_vs_s3fuse.html <![CDATA[s3fs vs s3fuse]]> s3fs vs s3fuse

Amazon S3をFUSEをつかってファイルシステムのように扱えるソフトウェアとしてs3fsが割と有名だけど、 s3fsにインスパイアされてできたs3fuseというものもある。

コードを見た感じs3fuseのほうが奇麗だったのでs3fuseいいなと思ったけど、 パフォーマンス悪いとアレなので簡単な試験をして比較をしてみた。

  • 必須条件
    • httpsを仕様
    • tokyo regionを使う
  • s3fs 1.61
# vi /etc/passwd-s3fs
accessKeyId:secretAccessKey
# /usr/local/bin/s3fs barbar -o url=https://s3-ap-northeast-1.amazonaws.com /mnt/
# ./perf.sh

prepare: removing all ...
--- create 1M file
1+0 records in
1+0 records out
1048576 bytes (1.0 MB) copied, 0.319318 s, 3.3 MB/s

real    0m0.591s
user    0m0.002s
sys     0m0.010s
--- done
--- create 5M file
1+0 records in
1+0 records out
5242880 bytes (5.2 MB) copied, 0.714269 s, 7.3 MB/s

real    0m0.977s
user    0m0.001s
sys     0m0.026s
--- done
--- create 10M file
1+0 records in
1+0 records out
10485760 bytes (10 MB) copied, 1.27239 s, 8.2 MB/s

real    0m1.542s
user    0m0.000s
sys     0m0.091s
--- done
--- create 50M file
1+0 records in
1+0 records out
52428800 bytes (52 MB) copied, 6.42343 s, 8.2 MB/s

real    0m6.676s
user    0m0.000s
sys     0m0.266s
--- done
--- create 100M file
1+0 records in
1+0 records out
104857600 bytes (105 MB) copied, 13.2172 s, 7.9 MB/s

real    0m13.503s
user    0m0.000s
sys     0m0.722s
--- done
--- create 200M file
1+0 records in
1+0 records out
209715200 bytes (210 MB) copied, 24.4316 s, 8.6 MB/s

real    0m24.688s
user    0m0.000s
sys     0m0.685s
--- done
--- cat 1M file

real    0m0.264s
user    0m0.000s
sys     0m0.002s
--- done
--- cat 5M file

real    0m0.685s
user    0m0.000s
sys     0m0.004s
--- done
--- cat 10M file

real    0m1.241s
user    0m0.000s
sys     0m0.009s
--- done
--- cat 50M file

real    0m5.526s
user    0m0.001s
sys     0m0.028s
--- done
--- cat 100M file

real    0m11.370s
user    0m0.000s
sys     0m0.054s
--- done
--- cat 200M file

real    0m21.721s
user    0m0.000s
sys     0m0.090s
--- done
prepare: removing all ...
--- create new non-zero byte 50 files

real    0m18.273s
user    0m0.031s
sys     0m0.078s
--- done
--- copy non-zero byte 50 files

real    0m22.181s
user    0m0.040s
sys     0m0.155s
--- done
--- cat non-zero byte 50 files

real    0m4.348s
user    0m0.041s
sys     0m0.093s
--- done
--- mv non-zero byte 50 files

real    0m16.025s
user    0m0.041s
sys     0m0.107s
--- done
--- rm non-zero byte 50 files

real    0m4.690s
user    0m0.051s
sys     0m0.070s
--- done
prepare: removing all ...
--- touch zero byte 50 files

real    0m22.865s
user    0m0.046s
sys     0m0.091s
--- done
--- touch already exist 50 files

real    0m18.976s
user    0m0.050s
sys     0m0.080s
--- done
prepare: create 50 files ...
--- mv directory

real    0m12.328s
user    0m0.001s
sys     0m0.002s
--- done
--- rm directory and 50 files

real    0m6.503s
user    0m0.001s
sys     0m0.013s
--- done
  • s3fuse 0.12
# umount /mnt
# vi /etc/s3fuse.conf
service=aws
bucket_name=barbar
auth_data=/etc/s3fuse/aws.key
aws_service_endpoint=s3-ap-northeast-1.amazonaws.com
# mkdir /etc/s3fuse
# vi /etc/s3fuse/aws.key
accessKeyId secretAccessKey
# /usr/local/bin/s3fuse -o config=/etc/s3fuse.conf  /mnt/
# ./perf.sh

prepare: removing all ...
--- create 1M file
1+0 records in
1+0 records out
1048576 bytes (1.0 MB) copied, 0.2001 s, 5.2 MB/s

real    0m0.397s
user    0m0.000s
sys     0m0.011s
--- done
--- create 5M file
1+0 records in
1+0 records out
5242880 bytes (5.2 MB) copied, 0.765021 s, 6.9 MB/s

real    0m0.888s
user    0m0.001s
sys     0m0.040s
--- done
--- create 10M file
1+0 records in
1+0 records out
10485760 bytes (10 MB) copied, 2.02609 s, 5.2 MB/s

real    0m2.142s
user    0m0.000s
sys     0m0.044s
--- done
--- create 50M file
1+0 records in
1+0 records out
52428800 bytes (52 MB) copied, 8.37144 s, 6.3 MB/s

real    0m8.499s
user    0m0.001s
sys     0m0.447s
--- done
--- create 100M file
1+0 records in
1+0 records out
104857600 bytes (105 MB) copied, 16.3551 s, 6.4 MB/s

real    0m16.469s
user    0m0.001s
sys     0m0.798s
--- done
--- create 200M file
1+0 records in
1+0 records out
209715200 bytes (210 MB) copied, 32.0709 s, 6.5 MB/s

real    0m32.183s
user    0m0.000s
sys     0m1.231s
--- done
--- cat 1M file

real    0m0.296s
user    0m0.001s
sys     0m0.002s
--- done
--- cat 5M file

real    0m1.499s
user    0m0.000s
sys     0m0.004s
--- done
--- cat 10M file

real    0m2.109s
user    0m0.000s
sys     0m0.008s
--- done
--- cat 50M file

real    0m6.392s
user    0m0.001s
sys     0m0.025s
--- done
--- cat 100M file

real    0m11.953s
user    0m0.000s
sys     0m0.046s
--- done
--- cat 200M file

real    0m23.393s
user    0m0.000s
sys     0m0.095s
--- done
prepare: removing all ...
--- create new non-zero byte 50 files

real    0m7.818s
user    0m0.029s
sys     0m0.090s
--- done
--- copy non-zero byte 50 files

real    0m13.207s
user    0m0.060s
sys     0m0.160s
--- done
--- cat non-zero byte 50 files

real    0m2.809s
user    0m0.055s
sys     0m0.071s
--- done
--- mv non-zero byte 50 files

real    0m10.916s
user    0m0.038s
sys     0m0.157s
--- done
--- rm non-zero byte 50 files

real    0m2.685s
user    0m0.049s
sys     0m0.082s
--- done
prepare: removing all ...
--- touch zero byte 50 files

real    0m8.230s
user    0m0.057s
sys     0m0.128s
--- done
--- touch already exist 50 files

real    0m5.916s
user    0m0.050s
sys     0m0.113s
--- done
prepare: create 50 files ...
--- mv directory

real    0m1.041s
user    0m0.000s
sys     0m0.003s
--- done
--- rm directory and 50 files

real    0m1.603s
user    0m0.003s
sys     0m0.002s
--- done

この2つを比較すると、大きいファイルの読み書きに関してはs3fsが早く、細かいファイルが複数ある場合はs3fuseの方が有利に見える。 複数のファイル処理にs3fuseが強いのは並列でリクエストを出しているのがうまく効いているためと思われる。 大きいファイル処理が遅いという点に関しては、実はs3fuseの方はupload時5Mをchunkサイズと見なし、 それ以上の場合にはamazon s3の定義するところのmultipart転送になる。(HTTPのmultipartとは無関係) そこで、s3fuseのchunkサイズをs3fsと同じ20Mにして再測定してみた。併せてダウンロードのチャンクサイズも20Mに変更した。

  • s3fuse 0.12 chunk 20M
# umount /mnt
# vi /etc/s3fuse.conf
service=aws
bucket_name=barbar
auth_data=/etc/s3fuse/aws.key
aws_service_endpoint=s3-ap-northeast-1.amazonaws.com
upload_chunk_size=20971520
download_chunk_size=20971520
# /usr/local/bin/s3fuse -o config=/etc/s3fuse.conf  /mnt/
# ./perf.sh

prepare: removing all ...
--- create 1M file
1+0 records in
1+0 records out
1048576 bytes (1.0 MB) copied, 0.193304 s, 5.4 MB/s

real    0m0.375s
user    0m0.001s
sys     0m0.014s
--- done
--- create 5M file
1+0 records in
1+0 records out
5242880 bytes (5.2 MB) copied, 0.793856 s, 6.6 MB/s

real    0m0.910s
user    0m0.000s
sys     0m0.069s
--- done
--- create 10M file
1+0 records in
1+0 records out
10485760 bytes (10 MB) copied, 1.3154 s, 8.0 MB/s

real    0m1.419s
user    0m0.000s
sys     0m0.093s
--- done
--- create 50M file
1+0 records in
1+0 records out
52428800 bytes (52 MB) copied, 8.77542 s, 6.0 MB/s

real    0m8.881s
user    0m0.000s
sys     0m0.617s
--- done
--- create 100M file
1+0 records in
1+0 records out
104857600 bytes (105 MB) copied, 16.4549 s, 6.4 MB/s

real    0m16.559s
user    0m0.001s
sys     0m1.035s
--- done
--- create 200M file
1+0 records in
1+0 records out
209715200 bytes (210 MB) copied, 34.1758 s, 6.1 MB/s

real    0m34.297s
user    0m0.000s
sys     0m2.148s
--- done
--- cat 1M file

real    0m0.361s
user    0m0.002s
sys     0m0.000s
--- done
--- cat 5M file

real    0m0.750s
user    0m0.001s
sys     0m0.007s
--- done
--- cat 10M file

real    0m1.289s
user    0m0.000s
sys     0m0.008s
--- done
--- cat 50M file

real    0m5.260s
user    0m0.000s
sys     0m0.025s
--- done
--- cat 100M file

real    0m10.232s
user    0m0.001s
sys     0m0.044s
--- done
--- cat 200M file

real    0m30.007s
user    0m0.000s
sys     0m0.095s
--- done
prepare: removing all ...
--- create new non-zero byte 50 files

real    0m7.427s
user    0m0.038s
sys     0m0.094s
--- done
--- copy non-zero byte 50 files

real    0m12.242s
user    0m0.045s
sys     0m0.169s
--- done
--- cat non-zero byte 50 files

real    0m2.525s
user    0m0.052s
sys     0m0.056s
--- done
--- mv non-zero byte 50 files

real    0m9.728s
user    0m0.057s
sys     0m0.131s
--- done
--- rm non-zero byte 50 files

real    0m2.547s
user    0m0.054s
sys     0m0.077s
--- done
prepare: removing all ...
--- touch zero byte 50 files

real    0m8.386s
user    0m0.049s
sys     0m0.104s
--- done
--- touch already exist 50 files

real    0m5.580s
user    0m0.047s
sys     0m0.110s
--- done
prepare: create 50 files ...
--- mv directory

real    0m1.116s
user    0m0.000s
sys     0m0.007s
--- done
--- rm directory and 50 files

real    0m1.619s
user    0m0.000s
sys     0m0.007s
--- done

パラメータは同じはずだけど、ほとんど変わらず。どうもs3fuseの方がmultipart実装が遅いと推測できる。 ただし、10Mの速度がs3fsより多少速くなっているので、multipartでなければs3fuseの方が速いと分かる。 そこで、いっそのことchunkサイズを200Mまであげて試してみた。

  • s3fuse 0.12 chunk 200M
# umount /mnt
# vi /etc/s3fuse.conf
service=aws
bucket_name=barbar
auth_data=/etc/s3fuse/aws.key
aws_service_endpoint=s3-ap-northeast-1.amazonaws.com
upload_chunk_size=209715200
download_chunk_size=209715200
# /usr/local/bin/s3fuse -o config=/etc/s3fuse.conf  /mnt/
# ./perf.sh

prepare: removing all ...
--- create 1M file
1+0 records in
1+0 records out
1048576 bytes (1.0 MB) copied, 0.193559 s, 5.4 MB/s

real    0m0.368s
user    0m0.001s
sys     0m0.008s
--- done
--- create 5M file
1+0 records in
1+0 records out
5242880 bytes (5.2 MB) copied, 0.699863 s, 7.5 MB/s

real    0m0.847s
user    0m0.000s
sys     0m0.015s
--- done
--- create 10M file
1+0 records in
1+0 records out
10485760 bytes (10 MB) copied, 1.32566 s, 7.9 MB/s

real    0m1.462s
user    0m0.000s
sys     0m0.076s
--- done
--- create 50M file
1+0 records in
1+0 records out
52428800 bytes (52 MB) copied, 6.20686 s, 8.4 MB/s

real    0m6.341s
user    0m0.000s
sys     0m0.306s
--- done
--- create 100M file
1+0 records in
1+0 records out
104857600 bytes (105 MB) copied, 11.572 s, 9.1 MB/s

real    0m12.279s
user    0m0.000s
sys     0m0.351s
--- done
--- create 200M file
1+0 records in
1+0 records out
209715200 bytes (210 MB) copied, 23.506 s, 8.9 MB/s

real    0m23.693s
user    0m0.000s
sys     0m0.741s
--- done
--- cat 1M file

real    0m0.285s
user    0m0.000s
sys     0m0.001s
--- done
--- cat 5M file

real    0m0.728s
user    0m0.000s
sys     0m0.005s
--- done
--- cat 10M file

real    0m1.414s
user    0m0.000s
sys     0m0.010s
--- done
--- cat 50M file

real    0m5.520s
user    0m0.000s
sys     0m0.031s
--- done
--- cat 100M file

real    0m11.365s
user    0m0.001s
sys     0m0.044s
--- done
--- cat 200M file

real    0m22.288s
user    0m0.000s
sys     0m0.094s
--- done
prepare: removing all ...
--- create new non-zero byte 50 files

real    0m8.945s
user    0m0.030s
sys     0m0.094s
--- done
--- copy non-zero byte 50 files

real    0m15.052s
user    0m0.060s
sys     0m0.160s
--- done
--- cat non-zero byte 50 files

real    0m3.204s
user    0m0.049s
sys     0m0.061s
--- done
--- mv non-zero byte 50 files

real    0m12.038s
user    0m0.068s
sys     0m0.122s
--- done
--- rm non-zero byte 50 files

real    0m3.055s
user    0m0.046s
sys     0m0.068s
--- done
prepare: removing all ...
--- touch zero byte 50 files

real    0m8.934s
user    0m0.043s
sys     0m0.136s
--- done
--- touch already exist 50 files

real    0m6.170s
user    0m0.048s
sys     0m0.097s
--- done
prepare: create 50 files ...
--- mv directory

real    0m1.345s
user    0m0.002s
sys     0m0.004s
--- done
--- rm directory and 50 files

real    0m1.961s
user    0m0.000s
sys     0m0.007s
--- done

大きなファイルの処理に関しても、s3fsと遜色無い値になった。 ただし、multipart使わないというのはよくない場合があるかもしれない。

まとめると、s3fuseはmultipartの処理がs3fsより遅いようなので、 大きなファイルを扱う場合はs3fsの方が良さそう。 ただし、場合によってはs3fuseはチャンクサイズを適切に調整してやる事で、 大きなファイルでもs3fsと遜色ないパフォーマンスを出す事が可能。 複数の小さなファイルの処理に関してはs3fuseにアドバンテージがある。

最後にs3fuseの欠点としては、s3fuseはバケット単位でしかマウントポイントを割り当てられないのに対し、 s3fsはバケットの中のパスに対してマウントポイントを割り当てられる。 まぁ、この機能あまり需要無い気がする。

s3fuseのほうがいいけど、s3fuseのmultipart速くなると最高なのにな。

]]>
Mon, 22 Oct 2012 00:00:00 +0900
http://blog.potix.jp/2012/09/29/backup_to_bitcasa.html http://blog.potix.jp/2012/09/29/backup_to_bitcasa.html <![CDATA[Bitcasaへバックアップ]]> Bitcasaへバックアップ

Bitcasaにさくらvpsや自宅サーバーのデータをバックアップするようにしてみた。

概要は以下のような感じ

../../../_images/backup_to_bitcasa.png

さくらのVPSや自前で持っているサーバーのデータをbackupサーバーにいったんrsyncで持ってきて、 rsyncで最新の状態にmirrorしたものをBitcasaに日付でディレクトリを掘ってコピーするという仕組み。

ちなみに、Bitcasaのlinux版は普通に配布されているので、書いてある通りにインストールして、 tightvncserverあたりでリモートからXが拾えるようにしてやればお手軽。

無限世代backup再び!!

コピーするデータ量はだいたい6.2G程度。 (各サーバーの /home /root /etc /var /usr/local をbackupしている)

# du -h /backup
.
.
.
6.2G    /backup

で、どのぐらいの転送時間かというと、だいたい6-7時間ぐらい。速度になおすとだいたい2.1Mbps程度。 Bitcasa設定でcacheを200Gにしているのでcacheが効いるのかもしれない。

Sep 25 21:25:44 backup logger: [Info] bitcasa transfer time 26614 sec
Sep 26 09:58:01 backup logger: [Info] bitcasa transfer time 21457 sec
Sep 27 09:04:45 backup logger: [Info] bitcasa transfer time 21840 sec
Sep 28 09:22:10 backup logger: [Info] bitcasa transfer time 22885 sec
Sep 29 09:13:39 backup logger: [Info] bitcasa transfer time 22372 sec

とはいえ、データ量が増えてくると1日でバックアップが終わらなくなるので、 並列でコピーとかも考えないといけない。

あと、特殊なファイルをバックアップしようとするとBitcasaのプログラムが刺さるっぽいので、 /procとか/devとか/tmpとか/var/runとかをバックアップするのは避けた方が無難。

]]>
Sat, 29 Sep 2012 00:00:00 +0900
http://blog.potix.jp/2012/09/01/hello_raspberry_pi.html http://blog.potix.jp/2012/09/01/hello_raspberry_pi.html <![CDATA[Raspberry Pi来たよ!]]> Raspberry Pi来たよ!

Raspberry Pi来たよ!。

まだ火を入れてないけど。

../../../_images/raspi01_s.jpg ../../../_images/raspi02_s.jpg ../../../_images/raspi03_s.jpg ../../../_images/raspi04_s.jpg ../../../_images/raspi05_s.jpg ]]>
Sat, 01 Sep 2012 00:00:00 +0900
http://blog.potix.jp/2012/09/01/centos_5_arping_bug.html http://blog.potix.jp/2012/09/01/centos_5_arping_bug.html <![CDATA[centos 5(6も?) arping バグ]]> centos 5(6も?) arping バグ

ちょっと前に調べたので。rstに慣れる意味で書いてみた。

今となっては使っている人はいないかもしれないけど centos 5 の arping はバグってるというお話。 ちなみに、 centos 6 はちゃんと調査してませんが、同じパッチがあたってるようなのでダメかもしれません。 というか centos 6 でも、後述する -c 偶数指定で指定した半分しかarpを出さない問題は残ってました。

実際に見た方が早いと思うので。

-bash-3.2$ date; sudo ./arping -I eth1 -b -c 3 2.2.2.2; echo $?
2012年  9月  1日 土曜日 20:34:21 JST
ARPING 2.2.2.2 from 10.1.1.220 eth1
Sent 3 probes (3 broadcast(s))
Received 0 response(s)
1
-bash-3.2$ date; sudo ./arping -I eth1 -b -c 3 2.2.2.2; echo $?
2012年  9月  1日 土曜日 20:34:26 JST
ARPING 2.2.2.2 from 10.1.1.220 eth1
0
-bash-3.2$ date; sudo ./arping -I eth1 -b -c 3 2.2.2.2; echo $?
2012年  9月  1日 土曜日 20:34:30 JST
ARPING 2.2.2.2 from 10.1.1.220 eth1
Sent 3 probes (3 broadcast(s))
Received 0 response(s)
1
-bash-3.2$ date; sudo ./arping -I eth1 -b -c 3 2.2.2.2; echo $?
2012年  9月  1日 土曜日 20:34:35 JST
ARPING 2.2.2.2 from 10.1.1.220 eth1
0

2.2.2.2 はありもしないアドレスなので、成功するはずが無いはずなのに、正常終了しています。 ちなみに、この現象は割とホストがたくさんぶら下がっていて arp がとびかってる環境で発生します。

なにはともあれ、src.rpmを展開してソースを見てみると、

$ mkdir ipuitls
$ wget ftp://ftp.pbone.net/mirror/vault.centos.org/5.8/os/SRPMS/iputils-20020927-46.el5.src.rpm
$ rpm2cpio iputils-20020927-46.el5.src.rpm | cpio -id
$ mkdir SOURCES
$ cp * SOURCES
$ rpmbuild -bp --define "_topdir `pwd`" iputils.spec
$ cd BUILD/iputils
$ make arping
$ less arping.c
 .
 . snip
 .
184         if ((timeout && MS_TDIFF(tv,start) > timeout*1000 + 500) ||
185                 ((count == 0) && (!timeout)))
186                 finish();
187
188         if (last.tv_sec==0 || MS_TDIFF(tv,last) > 500) {
189                 count--;
190                 send_pack(s, src, dst, &me[0], &he[0]);
191                 if (count == 0 && unsolicited)
192                         finish();
193         }
194         alarm(1);
 .
 . snip
 .
597                 if ((cc = recvfrom(s, packet, sizeof(packet), 0,
598                                    (struct sockaddr *)&from[0], (socklen_t*)&alen)) < 0) {
599                         perror("arping: recvfrom");
600                         continue;
601                 }
602                 sigemptyset(&sset);
603                 sigaddset(&sset, SIGALRM);
604                 sigaddset(&sset, SIGINT);
605                 sigprocmask(SIG_BLOCK, &sset, &osset);
606                 recv_pack(packet, cc, &from[0]);
607                 if(received == count)
608                         exit(0);
 .
 . snip
 .

この辺りがすべてを物語っていて、パケットを送信するとcountがデクリメントされて、 なにかしら arp っぽいものを受信していれば count の値と比較して同じなら正常終了する。 ちなみに、 received はちゃんとした reply の arp を受け取ればインクリメントされるので、 この場合、ありもしないホストからちゃんとした reply が帰ってくる事は無いので、常に0。 count が 0 になったときに arp っぽいなにかを受信すれば、正常終了となるバグ。 じゃー、なぜちゃんと失敗する場合があるのかというと、 count が 0 になった状態で SIGALRM をうければ finish() が呼ばれてちゃんと失敗と見なされる。 つまり、最後の最後に、 SIGALRM を受けるのが先か arp っぽいパケットを受けるのが先かで結果が変わる。

補足ですが、しれっと arp っぽいとか書いてますが、これはrecvfromで拾えたパケットという事です。 sokcet自体はPF_PACKETを指定しているので、その対象になる生パケットならなんでもよいという事です。

もう、勘のいい人は気づいたと思いますが。

-bash-3.2$ date; sudo ./arping -I eth1 -b -c 5 10.1.1.1
2012年  9月  1日 土曜日 21:20:23 JST
ARPING 10.1.1.1 from 10.1.1.220 eth1
Unicast reply from 10.1.1.1 [00:1B:FC:BD:CF:AA]  0.605ms
Unicast reply from 10.1.1.1 [00:1B:FC:BD:CF:AA]  0.631ms
Unicast reply from 10.1.1.1 [00:1B:FC:BD:CF:AA]  0.619ms
Unicast reply from 10.1.1.1 [00:1B:FC:BD:CF:AA]  0.648ms
Unicast reply from 10.1.1.1 [00:1B:FC:BD:CF:AA]  0.685ms
Sent 5 probes (5 broadcast(s))
Received 5 response(s)

-bash-3.2$ date; sudo ./arping -I eth1 -b -c 6 10.1.1.1
2012年  9月  1日 土曜日 21:20:30 JST
ARPING 10.1.1.1 from 10.1.1.220 eth1
Unicast reply from 10.1.1.1 [00:1B:FC:BD:CF:AA]  0.655ms
Unicast reply from 10.1.1.1 [00:1B:FC:BD:CF:AA]  0.639ms
Unicast reply from 10.1.1.1 [00:1B:FC:BD:CF:AA]  0.682ms

存在するホストに対して、 -c のカウントを偶数回にすると半分しか arp を送りません。 で、この問題を引き起こしているパッチは iputils-20020927-12-arping.patch のようです。

解決策ですが、ひとまず、以下のパッチをあてると count = 0 で正常終了する問題は回避できました。

--- arping.c.orig       2012-09-01 21:28:07.443016686 +0900
+++ arping.c    2012-09-01 21:29:22.710022302 +0900
@@ -373,6 +373,7 @@
 {
        int socket_errno;
        int ch;
+       int lcount;
        uid_t uid = getuid();

        s = socket(PF_PACKET, SOCK_DGRAM, 0);
@@ -400,7 +401,7 @@
                        quiet++;
                        break;
                case 'c':
-                       count = atoi(optarg);
+                       count = lcount = atoi(optarg);
                        break;
                case 'w':
                        timeout = atoi(optarg);
@@ -604,7 +605,7 @@
                sigaddset(&sset, SIGINT);
                sigprocmask(SIG_BLOCK, &sset, &osset);
                recv_pack(packet, cc, &from[0]);
-               if(received == count)
+               if(received == lcount)
                        exit(0);
                sigprocmask(SIG_SETMASK, &osset, NULL);
        }

ちなみに、

if(received == lcount)
      exit(0);

このexit(0)はfinish()が正解なんじゃないかと思うのだけれど、、、今回はそのままにしておきました。

centos 6 はというと

[root@hoge iputils]# cat /etc/issue
CentOS release 6.2 (Final)
Kernel \r on an \m

[root@hoge iputils]# arping -b -c 5 49.212.138.1 ; echo $?
ARPING 49.212.138.1 from 49.212.138.173 eth0
Unicast reply from 49.212.138.1 [00:00:5E:00:01:6B]  2.760ms
Unicast reply from 49.212.138.1 [00:00:5E:00:01:6B]  6.674ms
Unicast reply from 49.212.138.1 [00:00:5E:00:01:6B]  5.571ms
Unicast reply from 49.212.138.1 [00:00:5E:00:01:6B]  7.492ms
Unicast reply from 49.212.138.1 [00:00:5E:00:01:6B]  2.950ms
Sent 5 probes (5 broadcast(s))
Received 5 response(s)
0
[root@hoge iputils]# arping -b -c 4 49.212.138.1 ; echo $?
ARPING 49.212.138.1 from 49.212.138.173 eth0
Unicast reply from 49.212.138.1 [00:00:5E:00:01:6B]  2.727ms
Unicast reply from 49.212.138.1 [00:00:5E:00:01:6B]  2.550ms
0

えーっと、、、、、

どうやら、同じパッチがあたっているようです。

どなたに言えばいいんでしょう?

こういうの。

]]>
Sat, 01 Sep 2012 00:00:00 +0900
http://blog.potix.jp/2012/09/01/blog_update_commit_after_writing.html http://blog.potix.jp/2012/09/01/blog_update_commit_after_writing.html <![CDATA[日記の更新は書いてコミットするだけ]]> 日記の更新は書いてコミットするだけ

cronで1分置きにsvn updateして、revisionが違う場合は、

tinker -bでビルドするようにした。

これで、ターミナル上でrstを書いてコミットすれば、

ブログが更新されるようになった。

これで、日記を書くのが少し楽しくなるはず。

]]>
Sat, 01 Sep 2012 00:00:00 +0900
http://blog.potix.jp/2012/08/29/new_diary.html http://blog.potix.jp/2012/08/29/new_diary.html <![CDATA[新しい日記]]> 新しい日記

日記を新しくしてみんとて。

tinkererいいよtinkerer。

ひとまずテストかきこ。

ゆるゆると書く所存。

]]>
Wed, 29 Aug 2012 00:00:00 +0900