ブロックチェーンアプリケーション開発の教科書

2020-11-14

ブロックチェーンアプリケーション開発でおすすめの書籍を教えていただいたので読みました。

ブロックチェーンアプリケーション開発の教科書(リフロー版)

教科書というタイトル通り、実装方法だけではなくブロックチェーンの概要から実装に関する注意点まで網羅されています。Solidityによるコントラクトの実装と合わせて、ブロックチェーンアプリケーション開発に関して外観を眺めたい人にオススメです。

この書籍は出版が2018年ということもあり、書籍に掲載されているコードのいくつかはそのまま実行できないものがありました。これから読まれる方に向けて覚えている範囲でトラブルシュートの内容を残しておきます。

実行環境

  • macOS 10.15.5
  • Node v15.0.1
  • npm 7.0.3
  • go 1.14.4
  • Truffle v5.1.52 (core: 5.1.52)
  • Solidity v0.5.16 (solc-js)
  • Web3.js v1.2.9

トラブルシュート

Kindleリフロー版に基づいて記載しています。
対象はコントラクト実装に関するChapter6から8です。
Solidity構文に関する7.2から7.5は対象外です。

変更済みのコードはこちらにまとめています。

6.1.4.3: Gethの初期化処理

次のエラーが出ました。

Fatal: Failed to write genesis block: unsupported fork ordering: eip150Block not enabled, but eip155Block enabled at 0

6.1.4.1のgenesis.jsonのフォーマットが変わったので、次のように変更します。

{
  "config": {
    "chainId": 33,
    "homesteadBlock": 0,
    "eip150Block": 0,
    "eip155Block": 0,
    "eip158Block": 0,
    "byzantiumBlock": 0,
    "constantinopleBlock": 0,
    "petersburgBlock": 0,
    "istanbulBlock": 0
  },
  "alloc": {},
  "coinbase": "0x0000000000000000000000000000000000000000",
  "difficulty": "0x20000",
  "extraData": "",
  "gasLimit": "0x2fefd8",
  "nonce": "0x0000000000000042",
  "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "timestamp": "0x00"
}

Ref:

6.1.5.8: ロックの解除

personal.unlockAccountでエラーがでました。

GoError: Error: account unlock with HTTP access is forbidden at web3.js:6347:37(47)
        at native
        at <eval>:1:24(6)

6.1.4.6のgeth起動コマンドに--allow-insecure-unlockを追加すると実行可能になります。

geth --networkid "33" --nodiscover --datadir $DATA_DIR --rpc --rpcaddr "localhost" --rpcport "8545" --rpccorsdomain "*" --rpcapi "eth,net,web3,personal" \
    --allow-insecure-unlock \
    --targetgaslimit "20000000" console 2>> ${DATA_DIR}/error.log

unlockはデフォルトで機能OFFに変更されたようです。

Ref:

7.1.2.2: SimpleStorage.sol

Solidityのバージョンアップで記述方法が変わったので、次のように変更します。

// remix IDEサンプルsolidityコードのバージョンに合わせる
pragma solidity >=0.4.22 <0.7.0;

contract SimpleStorage {
    uint storedData;

    // publicが必要
    function set(uint x) public {
        storedData = x;
    }

    // publicが必要、constant廃止、viewに変える
    function get() public view returns (uint) {
        return storedData;
    }
}

7.1.2.9: SimpleStorageOwner.sol

コンストラクタの書式が変わったので、次のように変更します。

pragma solidity >=0.4.22 <0.7.0;

contract SimpleStorageOwner {
    uint storedData;
    address owner;

    // コンストラクタの記述が、construct() publicに変更
    constructor() public {
        owner = msg.sender;
    }
    // ...
}

Ref:

8.1.6.2: MetaCoin.sol

Solidityのバージョンアップで記述方法が変わったのですが、次のコマンドで取得するコードを実行すればOKでした。

$ truffle unbox metacoin

8.1.6.8: MetaCoinを変数に保存する

truffle(develop)> m = await MetaCoin.at("<address>")

atPromiseを返すようになったのでawait or コールバックで受け取ります。

Ref:

また、8.1.6.7の出力形式が変わったので、コントラクトのアドレスはcontract address:の値を使用します。

Replacing 'MetaCoin'
   --------------------
   > transaction hash:    0xdf84b6dc0debb81e22284bbc8b214c27a9c5b18b76e5ea9baef60c33bc3916b1
   > Blocks: 0            Seconds: 0
   > contract address:    0x1dA8f51aad5Eb8B2997AA9dc0FFD838900A2CA1C
   > block number:        9

8.1.6.9: getBalanceの呼び出し

web3.eth.getAccountsPromiseを返すようになったのでawait or コールバックで受け取ります。
8.1.6.11 - 8.1.6.17でweb3.eth.getAccountsを使用しているところも同様にaccounts[<index>]を指定します。

truffle(develop)> accounts = await web3.eth.getAccounts()
truffle(develop)> m.getBalance(accounts[0])

Ref:

8.2.2.1: OpenZeppelinのインストール

zeppelin-solidityはdeprecatedになりました。代わりにopenzeppelin-contractsを使用します。

$ npm init -f 
$ npm install @openzeppelin/contracts

Ref:

8.2.3.1: トークンのコントラクト

openzeppelin-contractsを使用するため記述が変わります。次のように変更します。

pragma solidity >=0.5.16 <0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// https://docs.openzeppelin.com/contracts/2.x/erc20-supply
contract DappsToken is ERC20 {
    string public name = "Dappstoken";
    string public symbol = "DTKN";
    uint public decimals = 18;

    constructor(uint256 initialSupply) public {
        _mint(msg.sender, initialSupply);

        // 8.3.2.15を実行するときは10e18を追加
        //_mint(msg.sender, initialSupply * (10 ** 18));
    }
}

8.2.4.3: マイグレーションの実行

8.1.6.8同様にawaitを使用します。

truffle(develop)> dappsToken = await DappsToken.at(DappsToken.address)

8.3.1.3: truffle.js

networks.development.network_idはgethのrunコマンドで指定している--networkidと同じ値を指定します。

8.3.2.7: MateMask Ether Faucetでのトランザクション発行

エラーが出てEtherを取得できませんでした。取得は別サイトでも可能なのでそれを利用します。いくつかあるようですが以下のサイトで取得できました。

Ethereum Faucet - Ropsten

Ref:

8.3.2.11: truffle.jsにRopstenを設定

truffleのバージョンアップで記述方法が変わったので、npmモジュールを追加して、コードを次のように変更します。

@truffle/hdwallet-providerモジュールの追加

truffle-config.jsで使用するモジュールを追加します。

$ npm install @truffle/hdwallet-provider

.secret

認証情報を.secretに定義します。MetaMaskで作成したアカウントのニーモニックをスペース区切りで入力します。

<nemonic1> <nemonic2> <nemonic3> ...

truffle-config.js

ファイル名がtruffle.jsからtruffle-config.jsに変わりました。
HDWalletProviderに渡すURLにaccessTokenではなくPROJECT SECRETを渡します。PROJECT SECRETInfuraで作成したプロジェクトで取得できます。

const HDWalletProvider = require('@truffle/hdwallet-provider');
const fs = require('fs');
const mnemonic = fs.readFileSync(".secret").toString().trim();

module.exports = {
  networks: {
    development: {
     host: "localhost",     // Localhost (default: none)
     port: 8545,            // Standard Ethereum port (default: none)
     network_id: "33",       // Any network (default: none)
    },
    ropsten: {
      provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/<Infura PROJECT SECRET>`),
      network_id: 3,       // Ropsten's id
      gas: 5500000,        // Ropsten has a lower block limit than mainnet
      confirmations: 2,    // # of confs to wait between deployments. (default: 0)
      timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
      skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
    },
  },
  ...
};

8.3.2.14, 8.3.2.15

migrateinitialSupplyのオーバーフローエラーが出るので、Contract側で1e18するように変更します。

2_deploy_dapps_token.js

const DappsToken = artifacts.require("./Dappstoken.sol");

module.exports = function(deployer){
    const initialSupply = 1000;
    // ここでは1000を渡す
    deployer.deploy(DappsToken, initialSupply, {
        gas: 2000000
    });
}

DappsToken.sol

pragma solidity >=0.5.16 <0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// https://docs.openzeppelin.com/contracts/2.x/erc20-supply
contract DappsToken is ERC20 {
    string public name = "Dappstoken";
    string public symbol = "DTKN";
    uint public decimals = 18;

    constructor(uint256 initialSupply) public {
        // ここでdecimalsに合わせ、1e18する
        _mint(msg.sender, initialSupply * (10 ** 18));
    }
}

8.3.2.20: 変数にトークンを設定

overflowで失敗するのでweb3.utils.toBNで変換した値を渡します。

truffle(ropsten)> d.transfer(“<receiver address>”, web3.utils.toBN(1e18))

8.3.2.21: 残高の確認

残高がwordに値を分割して表示されます。次のように表示すると一つの値で表示できます。

truffle(ropsten)> d.totalSupply().then(result => {console.log(result.toString())})

まとめ

Solidityや関連ライブラリのバージョンアップに伴い、コードのフォーマットが大きく変化していることで、ブロックチェーン関連技術の変化の速さを実感しますね。
調べていると、もうここまで開発が進んでいるのかという印象も。