ethereumでContractを生成してみる

ethereumのすごいところはethereum自体がコインのやり取りだけでなく「Ethereum Virtual Machine Code(EVM Code)」として
VMとして操作してそこでプログラマブルなことが行えるということって書いてありますがいまいちイメージがつかない。
イメージがつかない時は動かしましょうということでやっぱり動かしてみる

インストール&初期設定あたりは以下を見てください
garapon.hatenablog.com

EVM Codeで動く言語は何個かあるようなのですが、現状何もコンパイラインストールしていないのでなんもはいっていない。

> eth.getCompilers()
[""]

メジャー路線を突き進むべく、SolidityというのがJavaScriptに似ていてメジャーらしいのでそれでいきましょう。

Solidityコンパイラのインストール

Solidityのコンパイラsolcをインストール。これもaptでいける。ほんとethereumは楽。王道はいいですね。

$sudo apt-get install solc
$ ls -latr /usr/bin/solc
-rwxr-xr-x 1 root root 298792 12月 15 12:03 /usr/bin/solc

入りました。gethに設定しましょう。

> admin.setSolc("/usr/bin/solc")
I1216 15:19:42.838791    1972 solidity.go:114] solc, the solidity compiler commandline interface
Version: 0.2.0-0/Release-Linux/g++/int linked to libethereum-1.1.0-0/Release-Linux/g++/int

path: /usr/bin/solc
"solc, the solidity compiler commandline interface\nVersion: 0.2.0-0/Release-Linux/g++/int linked to libethereum-1.1.0-0/Release-Linux/g++/int\n\npath: /usr/bin/solc"
> eth.getCompilers()
["Solidity"]

できてますねー。

これを使ってContractを作成し、ブロックチェーンにContractを登録すると動き始めるようです。
まだなんかもやっとしています。

Contractでハローワールド作成

とりあえず書いてコンパイルしてみましょう

contract hellowEthereum {
    uint storedint;
    function get() constant returns (string retVal) {
        storedint++;
        return 'Hello World!!';
    }
    function getCnt() constant returns (uint retVal) {
        return storedint;
    }
}

ハローワールドして、呼出しごとに数をインクリメントしてみます。改行を全部なくして変数に突っ込みます。

> source = "contract hellowEthereum {    uint storedint;    function get() constant returns (string retVal) {        storedint++;        return 'Hello World!!';    }    function getCnt() constant returns (uint retVal) {        return storedint;    }}"

めっちゃみにくいっすね。コンパイルしましょう

> eth.compile.solidity(source)
{
  hellowEthereum: {
    code: "0x606060405260f0806100116000396000f3606060405260e060020a60003504636d4ce63c81146024578063e2ca8097146068575b005b6071600060608190528054600101905560c0604052600d60809081527f48656c6c6f20576f726c6421210000000000000000000000000000000000000060a0525b90565b60de6000546065565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f16801560d05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051918252519081900360200190f3",
    info: {
      abiDefinition: [{...}, {...}],
      compilerOptions: "--bin --abi --userdoc --devdoc --add-std --optimize -o /tmp/solc479871487",
      compilerVersion: "0.2.0",
      developerDoc: {
        methods: {}
      },
      language: "Solidity",
      languageVersion: "0.2.0",
      source: "contract hellowEthereum {    uint storedint;    function get() constant returns (string retVal) {        storedint++;        return 'Hello World!!';    }    function getCnt() constant returns (uint retVal) {        return storedint;    }}",
      userDoc: {
        methods: {}
      }
    }
  }
}

コンパイルできたようです。ただ、これだとコンパイルしただけで結果を受け取らないといけないので

compiled = eth.compile.solidity(source)

とする。全部が変数ですすむ世界ですね。

Contractのブロックチェーンへの登録

上記コンパイル後の情報の中から必要なものを集めます。

> abiDefinition = compiled.hellowEthereum.info.abiDefinition
[{
    constant: true,
    inputs: [],
    name: "get",
    outputs: [{
        name: "retVal",
        type: "string"
    }],
    type: "function"
}, {
    constant: true,
    inputs: [],
    name: "getCnt",
    outputs: [{
        name: "retVal",
        type: "uint256"
    }],
    type: "function"
}]
> compiledContract = eth.contract(abiDefinition)
{
  abi: [{
      constant: true,
      inputs: [],
      name: "get",
      outputs: [{...}],
      type: "function"
  }, {
      constant: true,
      inputs: [],
      name: "getCnt",
      outputs: [{...}],
      type: "function"
  }],
  eth: {
    _requestManager: {
      polls: {},
      provider: {
        send: function(),
        sendAsync: function()
      },
      timeout: null,
      poll: function(),
      reset: function(keepIsSyncing),
      send: function(data),
      sendAsync: function(data, callback),
      sendBatch: function(data, callback),
      setProvider: function(p),
      startPolling: function(data, pollId, callback, uninstall),
      stopPolling: function(pollId)
    },
    accounts: ["0x11b00fff3570ac74d66192ffc18d3621b0b3dc4e", "0xcf573ec35da6c35ded1a5416461de11bec0c89c0", "0x851cc0735985613418397b6a409e456393d2653e"],
    blockNumber: 34,
    coinbase: "0x11b00fff3570ac74d66192ffc18d3621b0b3dc4e",
    compile: {
      lll: function(),
      serpent: function(),
      solidity: function()
    },
    defaultAccount: undefined,
    defaultBlock: "latest",
    gasPrice: 50000000000,
    hashrate: 0,
    mining: false,
    pendingTransactions: null,
    syncing: false,
    call: function(),
    contract: function(abi),
    estimateGas: function(),
    filter: function(fil, callback),
    getAccounts: function(callback),
    getBalance: function(),
    getBlock: function(),
    getBlockNumber: function(callback),
    getBlockTransactionCount: function(),
    getBlockUncleCount: function(),
    getCode: function(),
    getCoinbase: function(callback),
    getCompilers: function(),
    getGasPrice: function(callback),
    getHashrate: function(callback),
    getMining: function(callback),
    getNatSpec: function(),
    getPendingTransactions: function(callback),
    getStorageAt: function(),
    getSyncing: function(callback),
    getTransaction: function(),
    getTransactionCount: function(),
    getTransactionFromBlock: function(),
    getTransactionReceipt: function(),
    getUncle: function(),
    getWork: function(),
    iban: function(iban),
    icapNamereg: function(),
    isSyncing: function(callback),
    namereg: function(),
    resend: function(),
    sendIBANTransaction: function(),
    sendRawTransaction: function(),
    sendTransaction: function(),
    sign: function(),
    signTransaction: function(),
    submitTransaction: function(),
    submitWork: function()
  },
  at: function(address, callback),
  getData: function(),
  new: function()
}
> contract = compiledContract.new({from:eth.accounts[0], data: compiled.hellowEthereum.code})
Please unlock account 11b00fff3570ac74d66192ffc18d3621b0b3dc4e.
Passphrase:
Account is now unlocked for this session.
{
  _eth: {
    _requestManager: {
      polls: {
        0x0: {...}
      },
      provider: {
        send: function(),
        sendAsync: function()
      },
      timeout: {},
      poll: function(),
      reset: function(keepIsSyncing),
      send: function(data),
      sendAsync: function(data, callback),
      sendBatch: function(data, callback),
      setProvider: function(p),
      startPolling: function(data, pollId, callback, uninstall),
      stopPolling: function(pollId)
    },
    accounts: ["0x11b00fff3570ac74d66192ffc18d3621b0b3dc4e", "0xcf573ec35da6c35ded1a5416461de11bec0c89c0", "0x851cc0735985613418397b6a409e456393d2653e"],
    blockNumber: 34,
    coinbase: "0x11b00fff3570ac74d66192ffc18d3621b0b3dc4e",
    compile: {
      lll: function(),
      serpent: function(),
      solidity: function()
    },
    defaultAccount: undefined,
    defaultBlock: "latest",
    gasPrice: 50000000000,
    hashrate: 0,
    mining: false,
    pendingTransactions: [{
        data: "0x606060405260f0806100116000396000f3606060405260e060020a60003504636d4ce63c81146024578063e2ca8097146068575b005b6071600060608190528054600101905560c0604052600d60809081527f48656c6c6f20576f726c6421210000000000000000000000000000000000000060a0525b90565b60de6000546065565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f16801560d05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051918252519081900360200190f3",
        from: "0x11b00fff3570ac74d66192ffc18d3621b0b3dc4e",
        gas: "90000",
        gasPrice: "50000000000",
        hash: "0x78f51436e46c4729e04331abc1c337ef60e8f987a8901ff793e707b81a205c99",
        nonce: "1",
        to: "",
        value: "0"
    }],
    syncing: false,
    call: function(),
    contract: function(abi),
    estimateGas: function(),
    filter: function(fil, callback),
    getAccounts: function(callback),
    getBalance: function(),
    getBlock: function(),
    getBlockNumber: function(callback),
    getBlockTransactionCount: function(),
    getBlockUncleCount: function(),
    getCode: function(),
    getCoinbase: function(callback),
    getCompilers: function(),
    getGasPrice: function(callback),
    getHashrate: function(callback),
    getMining: function(callback),
    getNatSpec: function(),
    getPendingTransactions: function(callback),
    getStorageAt: function(),
    getSyncing: function(callback),
    getTransaction: function(),
    getTransactionCount: function(),
    getTransactionFromBlock: function(),
    getTransactionReceipt: function(),
    getUncle: function(),
    getWork: function(),
    iban: function(iban),
    icapNamereg: function(),
    isSyncing: function(callback),
    namereg: function(),
    resend: function(),
    sendIBANTransaction: function(),
    sendRawTransaction: function(),
    sendTransaction: function(),
    sign: function(),
    signTransaction: function(),
    submitTransaction: function(),
    submitWork: function()
  },
  abi: [{
      constant: true,
      inputs: [],
      name: "get",
      outputs: [{...}],
      type: "function"
  }, {
      constant: true,
      inputs: [],
      name: "getCnt",
      outputs: [{...}],
      type: "function"
  }],
  address: undefined,
  transactionHash: "0x78f51436e46c4729e04331abc1c337ef60e8f987a8901ff793e707b81a205c99"
}

登録できました。ただ採掘されていないので 「address: undefined,」になっていますね。
さてまた採掘してからみてみると、、、

miner.start()
miner.stop()
> contract
{
  _eth: {
    _requestManager: {
      polls: {},
      provider: {
        send: function(),
        sendAsync: function()
      },
      timeout: null,
      poll: function(),
      reset: function(keepIsSyncing),
      send: function(data),
      sendAsync: function(data, callback),
      sendBatch: function(data, callback),
      setProvider: function(p),
      startPolling: function(data, pollId, callback, uninstall),
      stopPolling: function(pollId)
    },
    accounts: ["0x11b00fff3570ac74d66192ffc18d3621b0b3dc4e", "0xcf573ec35da6c35ded1a5416461de11bec0c89c0", "0x851cc0735985613418397b6a409e456393d2653e"],
    blockNumber: 40,
    coinbase: "0x11b00fff3570ac74d66192ffc18d3621b0b3dc4e",
    compile: {
      lll: function(),
      serpent: function(),
      solidity: function()
    },
    defaultAccount: undefined,
    defaultBlock: "latest",
    gasPrice: 50000000000,
    hashrate: 0,
    mining: false,
    pendingTransactions: null,
    syncing: false,
    call: function(),
    contract: function(abi),
    estimateGas: function(),
    filter: function(fil, callback),
    getAccounts: function(callback),
    getBalance: function(),
    getBlock: function(),
    getBlockNumber: function(callback),
    getBlockTransactionCount: function(),
    getBlockUncleCount: function(),
    getCode: function(),
    getCoinbase: function(callback),
    getCompilers: function(),
    getGasPrice: function(callback),
    getHashrate: function(callback),
    getMining: function(callback),
    getNatSpec: function(),
    getPendingTransactions: function(callback),
    getStorageAt: function(),
    getSyncing: function(callback),
    getTransaction: function(),
    getTransactionCount: function(),
    getTransactionFromBlock: function(),
    getTransactionReceipt: function(),
    getUncle: function(),
    getWork: function(),
    iban: function(iban),
    icapNamereg: function(),
    isSyncing: function(callback),
    namereg: function(),
    resend: function(),
    sendIBANTransaction: function(),
    sendRawTransaction: function(),
    sendTransaction: function(),
    sign: function(),
    signTransaction: function(),
    submitTransaction: function(),
    submitWork: function()
  },
  abi: [{
      constant: true,
      inputs: [],
      name: "get",
      outputs: [{...}],
      type: "function"
  }, {
      constant: true,
      inputs: [],
      name: "getCnt",
      outputs: [{...}],
      type: "function"
  }],
  address: "0x2d37e8d3c1941039dabf06f7db30d5713132615f",
  transactionHash: "0x78f51436e46c4729e04331abc1c337ef60e8f987a8901ff793e707b81a205c99",
  allEvents: function(),
  get: function(),
  getCnt: function()
}

アドレスに値が入りましたね!「address: "0x2d37e8d3c1941039dabf06f7db30d5713132615f",」
そしたら契約オブジェクトを作ります。

> cnt = eth.contract(abiDefinition).at('0x2d37e8d3c1941039dabf06f7db30d5713132615f');
{
  _eth: {
    _requestManager: {
      polls: {},
      provider: {
        send: function(),
        sendAsync: function()
      },
      timeout: null,
      poll: function(),
      reset: function(keepIsSyncing),
      send: function(data),
      sendAsync: function(data, callback),
      sendBatch: function(data, callback),
      setProvider: function(p),
      startPolling: function(data, pollId, callback, uninstall),
      stopPolling: function(pollId)
    },
    accounts: ["0x11b00fff3570ac74d66192ffc18d3621b0b3dc4e", "0xcf573ec35da6c35ded1a5416461de11bec0c89c0", "0x851cc0735985613418397b6a409e456393d2653e"],
    blockNumber: 40,
    coinbase: "0x11b00fff3570ac74d66192ffc18d3621b0b3dc4e",
    compile: {
      lll: function(),
      serpent: function(),
      solidity: function()
    },
    defaultAccount: undefined,
    defaultBlock: "latest",
    gasPrice: 50000000000,
    hashrate: 0,
    mining: false,
    pendingTransactions: null,
    syncing: false,
    call: function(),
    contract: function(abi),
    estimateGas: function(),
    filter: function(fil, callback),
    getAccounts: function(callback),
    getBalance: function(),
    getBlock: function(),
    getBlockNumber: function(callback),
    getBlockTransactionCount: function(),
    getBlockUncleCount: function(),
    getCode: function(),
    getCoinbase: function(callback),
    getCompilers: function(),
    getGasPrice: function(callback),
    getHashrate: function(callback),
    getMining: function(callback),
    getNatSpec: function(),
    getPendingTransactions: function(callback),
    getStorageAt: function(),
    getSyncing: function(callback),
    getTransaction: function(),
    getTransactionCount: function(),
    getTransactionFromBlock: function(),
    getTransactionReceipt: function(),
    getUncle: function(),
    getWork: function(),
    iban: function(iban),
    icapNamereg: function(),
    isSyncing: function(callback),
    namereg: function(),
    resend: function(),
    sendIBANTransaction: function(),
    sendRawTransaction: function(),
    sendTransaction: function(),
    sign: function(),
    signTransaction: function(),
    submitTransaction: function(),
    submitWork: function()
  },
  abi: [{
      constant: true,
      inputs: [],
      name: "get",
      outputs: [{...}],
      type: "function"
  }, {
      constant: true,
      inputs: [],
      name: "getCnt",
      outputs: [{...}],
      type: "function"
  }],
  address: "0x2d37e8d3c1941039dabf06f7db30d5713132615f",
  transactionHash: null,
  allEvents: function(),
  get: function(),
  getCnt: function()
}

これで僕のつくったハローワールドにアクセスできちゃいます。

> cnt.get()
"Hello World!!"
> cnt.getCnt()
0
> cnt.get()
"Hello World!!"
> cnt.getCnt()
0
>

おおおおーーーーー。しかしトランザクションを発行しているわけではないのでcnt.getCnt()は変わらないですね。
なのでトランザクションを発行して呼び出してみます

> cnt.get.sendTransaction({from:eth.accounts[0]})
"0x97fa2b991e7722e01ba264d09ae8a8f2129b81f399945537f9b69c9c3c763a69"
> miner.start()
> miner.stop()
true
>  cnt.getCnt()
1

おおーーートランザクションが発行されて1になりました!!!
すごい!!!!