卒論から始まるBlockchain

卒論から始まるBlockchain

俺の屍を越えて行け

Truffle, OpenZeppelin, Ganache を使用してERC721を作成する

まえおき

タイトル通りTruffle, OpenZeppelin を使用してERC721を作成してみます。参考にしたのはERC721トークンの実装とRinkebyデプロイという記事。これをちょびっとだけ改造したものになります。 新規プロジェクト作成〜ERC721発行までの手順とかを毎回調べるのは苦痛のなのでまとめておきます。 バージョンなどの変更等で気づいたこととかあったら更新するかもしれません。(気づいたら教えてください) なおTruffle, OpenZeppelinの説明はしません。 - Truffleを入れる。 - そもそもSolidityとかNode.jsが入っていない人はそれを入れましょう - そもそもTruffleって何やねんって人はTruffleチュートリアルをやってみるといいかも。(幸いDocBase上にはTruffleチュートリアルをやった際のまとめがあるため、それを参考してみてね) - Truffle チュートリアル

環境

$ truffle version
Truffle v5.0.26 (core: 5.0.26)
Solidity v0.5.0 (solc-js)
Node v10.13.0
Web3.js v1.0.0-beta.37

全体の大まかな流れ

コントラクト作成までの大まかな流れは以下の通り。 1. ディレクトリ作成 2. 新しいTruffleプロジェクトを作る 3. OpenZeppleinインストール 4. コントラクトを作る 5. コンパイルを通す 5. マイグレーションファイルを作成する 6. テスト作成 7. テストを通す 7. デプロイ

今回はデプロイ後新規でNFTも発行してみます。

今回作成するスマートコントラクト概要

  • 名前はMochimochiNFT、単位はMTC
  • デプロイと同時に1MTC発行
  • 誰でも追加発行、譲渡可能

ディレクトリ作成

  • ディレクトリを作ってそこでTruffleの新しいプロジェクトを作る(ここではerc721-testというディレクトリを作成)
$ mkdir erc721-test
$ cd erc721-test/

Truffleプロジェクト作成

Truffleプロジェクトを新規作成

$ truffle init

作成すると以下の様なものが作成される。

$ tree
|── contracts
│   └── Migrations.sol
|── migrations
│   └── 1_initial_migration.js
|── test
└── truffle-config.js

OpenZeppleinインストール

npm使ってOpenZeppleinをインストール。

$ npm init -y
$ npm install openzeppelin-solidity

すると以下の様なディレクトリが作成される。見た通り、npmで入れるとOpenZeppelinライブラリは全てnode_modulesの中に入ることになる。

$ tree
.
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── node_modules
│   └── openzeppelin-solidity
│       ├── CHANGELOG.md
│       ├── LICENSE
│       ├── README.md
│       ├── build
│       │   └── contracts
│       │       ├── Address.json
│       │       ├── AllowanceCrowdsale.json
│       │       ├── Arrays.json
│       │       ├── CappedCrowdsale.json
│       │       ├── CapperRole.json
│       │       ├── ConditionalEscrow.json
│       │       ├── Counters.json
│       │       ├── Crowdsale.json
│       │       ├── ECDSA.json
│       │       ├── ERC165.json
│       │       ├── ERC165Checker.json
│       │       ├── ERC1820Implementer.json
│       │       ├── ERC20.json
│       │       ├── ERC20Burnable.json
│       │       ├── ERC20Capped.json
│       │       ├── ERC20Detailed.json
│       │       ├── ERC20Metadata.json
│       │       ├── ERC20Migrator.json
│       │       ├── ERC20Mintable.json
│       │       ├── ERC20Pausable.json
│       │       ├── ERC20Snapshot.json
│       │       ├── ERC721.json
│       │       ├── ERC721Burnable.json
│       │       ├── ERC721Enumerable.json
│       │       ├── ERC721Full.json
│       │       ├── ERC721Holder.json
│       │       ├── ERC721Metadata.json
│       │       ├── ERC721MetadataMintable.json
│       │       ├── ERC721Mintable.json
│       │       ├── ERC721Pausable.json
│       │       ├── ERC777.json
│       │       ├── Escrow.json
│       │       ├── FinalizableCrowdsale.json
│       │       ├── IERC165.json
│       │       ├── IERC1820Implementer.json
│       │       ├── IERC1820Registry.json
│       │       ├── IERC20.json
│       │       ├── IERC721.json
│       │       ├── IERC721Enumerable.json
│       │       ├── IERC721Full.json
│       │       ├── IERC721Metadata.json
│       │       ├── IERC721Receiver.json
│       │       ├── IERC777.json
│       │       ├── IERC777Recipient.json
│       │       ├── IERC777Sender.json
│       │       ├── IncreasingPriceCrowdsale.json
│       │       ├── IndividuallyCappedCrowdsale.json
│       │       ├── Math.json
│       │       ├── MerkleProof.json
│       │       ├── MintedCrowdsale.json
│       │       ├── MinterRole.json
│       │       ├── Ownable.json
│       │       ├── Pausable.json
│       │       ├── PausableCrowdsale.json
│       │       ├── PauserRole.json
│       │       ├── PaymentSplitter.json
│       │       ├── PostDeliveryCrowdsale.json
│       │       ├── PullPayment.json
│       │       ├── ReentrancyGuard.json
│       │       ├── RefundEscrow.json
│       │       ├── RefundableCrowdsale.json
│       │       ├── RefundablePostDeliveryCrowdsale.json
│       │       ├── Roles.json
│       │       ├── SafeERC20.json
│       │       ├── SafeMath.json
│       │       ├── Secondary.json
│       │       ├── SignatureBouncer.json
│       │       ├── SignedSafeMath.json
│       │       ├── SignerRole.json
│       │       ├── TimedCrowdsale.json
│       │       ├── TokenTimelock.json
│       │       ├── TokenVesting.json
│       │       ├── WhitelistAdminRole.json
│       │       ├── WhitelistCrowdsale.json
│       │       ├── WhitelistedRole.json
│       │       └── __unstable__TokenVault.json
│       ├── contracts
│       │   ├── ARCHITECTURE.md
│       │   ├── access
│       │   │   ├── README.md
│       │   │   ├── Roles.sol
│       │   │   └── roles
│       │   │       ├── CapperRole.sol
│       │   │       ├── MinterRole.sol
│       │   │       ├── PauserRole.sol
│       │   │       ├── SignerRole.sol
│       │   │       ├── WhitelistAdminRole.sol
│       │   │       └── WhitelistedRole.sol
│       │   ├── crowdsale
│       │   │   ├── Crowdsale.sol
│       │   │   ├── README.md
│       │   │   ├── distribution
│       │   │   │   ├── FinalizableCrowdsale.sol
│       │   │   │   ├── PostDeliveryCrowdsale.sol
│       │   │   │   ├── RefundableCrowdsale.sol
│       │   │   │   └── RefundablePostDeliveryCrowdsale.sol
│       │   │   ├── emission
│       │   │   │   ├── AllowanceCrowdsale.sol
│       │   │   │   └── MintedCrowdsale.sol
│       │   │   ├── price
│       │   │   │   └── IncreasingPriceCrowdsale.sol
│       │   │   └── validation
│       │   │       ├── CappedCrowdsale.sol
│       │   │       ├── IndividuallyCappedCrowdsale.sol
│       │   │       ├── PausableCrowdsale.sol
│       │   │       ├── TimedCrowdsale.sol
│       │   │       └── WhitelistCrowdsale.sol
│       │   ├── cryptography
│       │   │   ├── ECDSA.sol
│       │   │   ├── MerkleProof.sol
│       │   │   └── README.md
│       │   ├── drafts
│       │   │   ├── Counters.sol
│       │   │   ├── ERC1046
│       │   │   │   └── ERC20Metadata.sol
│       │   │   ├── ERC20Migrator.sol
│       │   │   ├── ERC20Snapshot.sol
│       │   │   ├── README.md
│       │   │   ├── SignatureBouncer.sol
│       │   │   ├── SignedSafeMath.sol
│       │   │   └── TokenVesting.sol
│       │   ├── introspection
│       │   │   ├── ERC165.sol
│       │   │   ├── ERC165Checker.sol
│       │   │   ├── ERC1820Implementer.sol
│       │   │   ├── IERC165.sol
│       │   │   ├── IERC1820Implementer.sol
│       │   │   ├── IERC1820Registry.sol
│       │   │   └── README.md
│       │   ├── lifecycle
│       │   │   └── Pausable.sol
│       │   ├── math
│       │   │   ├── Math.sol
│       │   │   ├── README.md
│       │   │   └── SafeMath.sol
│       │   ├── ownership
│       │   │   ├── Ownable.sol
│       │   │   ├── README.md
│       │   │   └── Secondary.sol
│       │   ├── payment
│       │   │   ├── PaymentSplitter.sol
│       │   │   ├── PullPayment.sol
│       │   │   ├── README.md
│       │   │   └── escrow
│       │   │       ├── ConditionalEscrow.sol
│       │   │       ├── Escrow.sol
│       │   │       ├── README.md
│       │   │       └── RefundEscrow.sol
│       │   ├── token
│       │   │   ├── ERC20
│       │   │   │   ├── ERC20.sol
│       │   │   │   ├── ERC20Burnable.sol
│       │   │   │   ├── ERC20Capped.sol
│       │   │   │   ├── ERC20Detailed.sol
│       │   │   │   ├── ERC20Mintable.sol
│       │   │   │   ├── ERC20Pausable.sol
│       │   │   │   ├── IERC20.sol
│       │   │   │   ├── README.md
│       │   │   │   ├── SafeERC20.sol
│       │   │   │   └── TokenTimelock.sol
│       │   │   ├── ERC721
│       │   │   │   ├── ERC721.sol
│       │   │   │   ├── ERC721Burnable.sol
│       │   │   │   ├── ERC721Enumerable.sol
│       │   │   │   ├── ERC721Full.sol
│       │   │   │   ├── ERC721Holder.sol
│       │   │   │   ├── ERC721Metadata.sol
│       │   │   │   ├── ERC721MetadataMintable.sol
│       │   │   │   ├── ERC721Mintable.sol
│       │   │   │   ├── ERC721Pausable.sol
│       │   │   │   ├── IERC721.sol
│       │   │   │   ├── IERC721Enumerable.sol
│       │   │   │   ├── IERC721Full.sol
│       │   │   │   ├── IERC721Metadata.sol
│       │   │   │   ├── IERC721Receiver.sol
│       │   │   │   └── README.md
│       │   │   └── ERC777
│       │   │       ├── ERC777.sol
│       │   │       ├── IERC777.sol
│       │   │       ├── IERC777Recipient.sol
│       │   │       ├── IERC777Sender.sol
│       │   │       └── README.md
│       │   └── utils
│       │       ├── Address.sol
│       │       ├── Arrays.sol
│       │       ├── README.md
│       │       └── ReentrancyGuard.sol
│       ├── package.json
│       └── test
│           └── behaviors
│               └── access
│                   └── roles
│                       └── PublicRole.behavior.js
├── package-lock.json
├── package.json
├── test
└── truffle-config.js

コントラクト作成

コントラクトを作成していく。フォルダーはcontracts/ディレクトリ下にAsset.solファイルを作成する。 まず、コード内容は以下の通り

pragma solidity ^0.5.0;

import "../node_modules/openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol";

contract Test is ERC721Full {   // (1)
    constructor(   
        string memory name,     // (2)
        string memory symbol,       // (2)
        uint tokenId,   // (2)
        string memory tokenURI      // (2)
    )

    ERC721Full(name, symbol) public {   // (3)
        _mint(msg.sender, tokenId);     // (4)
        _setTokenURI(tokenId, tokenURI);    //(5)
    }
    
        function mint(uint256 tokenId) public{  // (6)
        _mint(msg.sender, tokenId);     // (7)
    }
}

解説

今回はERC721Ful.solを継承させます。ERC721と言っても色々ライブラリがありますが、とりあえず全部載せであるこれを入れておきます。

(1) contract

contract 内に処理内容を書いていく。オブジェクト指向言語での「クラス」的なもの。以下に基本的な構文などは書いてある。 - Solidity Dcoument v0.5.10 - 基本的な記法

(2) 各パラメータ

  • nameトークン名
  • symbolトークン単位
  • tokenId:アセット識別子
  • tokenURLトークンの詳細譲歩を示すURLファイルのパス

(3) ERC721Full(name, symbol) public {

今回のスマコンでは ERC721Full関数がデプロイ時に実行される。挙動はERC721Full(name, symbol) public {と書いてあるのでERC721.solを見てみる。

contract ERC721Full is ERC721, ERC721Enumerable, ERC721Metadata {
    constructor (string memory name, string memory symbol) public ERC721Metadata(name, symbol) {
        // solhint-disable-previous-line no-empty-blocks
    }

ここでは引数として受け取ったname, symbolERC721Metadata.sol に渡す。

    bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f;

    /**
     * @dev Constructor function
     */

    constructor (string memory name, string memory symbol) public {
        _name = name;
        _symbol = symbol;

        // register the supported interfaces to conform to ERC721 via ERC165
        _registerInterface(_INTERFACE_ID_ERC721_METADATA);
    }

同じように引数として受け取ったname, symbol_name, _symbol に渡す。 その後 _registerInterface を実行する。 これはERC165.sol に書かれているので見てみます。

    bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;

    mapping(bytes4 => bool) private _supportedInterfaces;

    constructor () internal {
        // Derived contracts need only register support for their own interfaces,
        // we register support for ERC165 itself here
        _registerInterface(_INTERFACE_ID_ERC165);
    }
・・・・・・・・・・・・・
    function _registerInterface(bytes4 interfaceId) internal {
        require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
        _supportedInterfaces[interfaceId] = true;
    }

該当箇所だけ抜き取ったのが以下のコード。 constructor 内の _registerInterface が実行されます。

contract ERC165 is IERC165 {
    /*
     * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
     */
    bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;

    /**
     * @dev Mapping of interface ids to whether or not it's supported.
     */
    mapping(bytes4 => bool) private _supportedInterfaces;

    constructor () internal {
        // Derived contracts need only register support for their own interfaces,
        // we register support for ERC165 itself here
        _registerInterface(_INTERFACE_ID_ERC165);
    }

    /**
     * @dev See `IERC165.supportsInterface`.
     *
     * Time complexity O(1), guaranteed to always use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool) {
        return _supportedInterfaces[interfaceId];
    }

    /**
     * @dev Registers the contract as an implementer of the interface defined by
     * `interfaceId`. Support of the actual ERC165 interface is automatic and
     * registering its interface id is not required.
     *
     * See `IERC165.supportsInterface`.
     *
     * Requirements:
     *
     * - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`).
     */
    function _registerInterface(bytes4 interfaceId) internal {
        require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
        _supportedInterfaces[interfaceId] = true;
    }
}

ちなみに変数の先頭によく使われている_は何なのか調べてみると同じように疑問に思っている人を見かけたので貼っておく。どうも関係修飾子を使う際に使われるものらしい。暗黙のルール的な感じです。 What does _(underscore) do?

ただしmodifierを書く際は最後に_; が必ず必要になります。 modifierって何やねんてんって方は以下の記事をどうぞ

【Solidity基礎】modifier修飾子について

(4) _mint(msg.sender, tokenId);

_mint が新規NFTの発行のための関数になっている。ERC721Enumerable.sol に記述されているのが以下のもの。

    function _mint(address to, uint256 tokenId) internal {
        super._mint(to, tokenId);

        _addTokenToOwnerEnumeration(to, tokenId);

        _addTokenToAllTokensEnumeration(tokenId);
    }

第一引数にaddress 第二引数tokenIdを入れることで、それぞれをNFTに紐づけている。今回はmsg.sender(ここではコントラクトをデプロイした人)と渡すuint256型の引数がtokenId になります。

superは多重継承問題をよしなにしてくれるメソッド。詳しくはSolidityの多重継承やダイヤモンド継承の挙動や優先順位についてを見てください。

_addTokenToOwnerEnumeration(to, tokenId);_addTokenToAllTokensEnumeration(tokenId); はそれぞれ以下の通り。

    function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
        _ownedTokensIndex[tokenId] = _ownedTokens[to].length;
        _ownedTokens[to].push(tokenId);
    }

    /**
     * @dev Private function to add a token to this extension's token tracking data structures.
     * @param tokenId uint256 ID of the token to be added to the tokens list
     */
    function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
        _allTokensIndex[tokenId] = _allTokens.length;
        _allTokens.push(tokenId);
    }

(5) _setTokenURI(tokenId, tokenURI);

最後にERC721Metadata.sol_setTokenURI を呼び出し tokenIdtokenURI を関連づける。 _setTokenURI は以下の通り

    function _setTokenURI(uint256 tokenId, string memory uri) internal {
        require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
        _tokenURIs[tokenId] = uri;
    }

_exists に関しては以下のようになっています。

    function _exists(uint256 tokenId) internal view returns (bool) {
        address owner = _tokenOwner[tokenId];
        return owner != address(0);
    }

(6) function mint(uint256 tokenId) public{

ここでは新規NFT発行の関数を作成している。最初の宣言時に function を宣言。引数にはuint256型を指定。実際に発行する時の関数名がmint なので名前も mint 関数とした。

(7) _mint(msg.sender, tokenId);

(4) でも実行した_mint を呼び出している。msg.sender とすることで、この関数を呼び出した人がaddressと紐付くことになる。

コンパイル

truffle compile を叩くだけ。成功するまで愚直に修正しましょう。

テスト作成

test/ ディレクトリ配下に Asset.js を作成。テスト内容は超適当。とりあえずシンボル名と名前があっているかの確認だけ。テストに関しては詳しく別記事を書きます。(知らんけど)

const Asset = artifacts.require('../contracts/Assets.sol');

contract('Asset', function ([creator, ...accounts]) {
    const name = "MochiMochiNFT";
    const tokenId = 1;

    it("...is going to confirm the token name and tokenId.", async () => {
        const assetInstance = await Asset.deployed();
        let tokenName = await assetInstance.name();
        let owner = await assetInstance.ownerOf(tokenId);

        assert.equal(tokenName, name, "Name isn't the same.");
        assert.equal(creator, owner, "Account isn't the same.");
     });
});

コントラクトデプロイ後、トークン名とtokeIdに紐づいたアカウントアドレスを取得。

その後、指定したトークン名(Simple Asset)とtokenIdに紐づいたアドレスがトークン作成者のアカウントアドレスを一致しているかを検証。

truffle testで実行。特に問題なければ成功するはず。ちなみにディレクトリ指定も出来ます。

マイグレーションの作成

コントラクト作成後、マイグレーションを作成します。 ここでは2_deploy_assets.jsmigrattions配下に作成。ここではERC721トークンに必要なパラメーターを定義する。コードは以下の通り。

const Asset = artifacts.require("../contracts/Asset.sol")

module.exports = async(deployer, network, accounts) => {
    const name = "MochiMochiNFT";
    const symbol = "MTC";
    const tokenId = 1;
    const tokenURI = "https://ipfs.io/ipfs/QmYMYdJqSrTNB3iwXzmYfGdjAymuXvHAJuum9zzT7RV7N1";

    await deployer.deploy(
        Asset,
        name,
        symbol,
        tokenId,
        tokenURI
    );
};

デプロイ

まずはEthereumネットワーク繋げる必要があるので、それらの設定が書かれているファイルtruffle-config.jsを開き、以下のようにする。実際には途中に書いてあるコメントを元に戻して、port番号を変えるだけ。

また今回はGanacheを利用する。これはmetamaskとも接続済みという前提で進める。(この記事は前回のTruffleチュートリアル後に書いているため)

 module.exports = {

   networks: {

    development: {
     host: "127.0.0.1",     // Localhost (default: none)
     port: 7545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)
    },
    },
      mocha: {
  },
  compilers: {
    solc: {
    }
  }
}

それが出来たらGanacheを立ち上げる。その後、追加のNFT発行等もコンソール画面で色々やりたいので、Truffleコンソール画面を立ち上げる。 Truffle consoleと打つとターミナルにtruffle(development)>と表示されるようになる。

では早速 migrateと打ってみる。実行結果が以下の通り。(ここではすでにmigrateをしてしまっていたので、--reset をしているが初めての場合は気にしなくていい。)

truffle(development)> truffle(development)> migrate --reset

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.


Starting migrations...
======================
> Network name:    'development'
> Network id:      5777
> Block gas limit: 0x6691b7


1_initial_migration.js
======================

   Replacing 'Migrations'
   ----------------------
   > transaction hash:    0xe7058b58cdc9bb62b4f344d167adfd6fa16c9ca0fa49e382e305913f5c60e5ec
   > Blocks: 0            Seconds: 0
   > contract address:    0x9C8390a018Eb0CA1Eda560284b5BA8b458092024
   > block number:        29
   > block timestamp:     1563933879
   > account:             0xf7654E6fC8Ca3e7dB6C615D49C3e184A8A39E3DB
   > balance:             99.54674476
   > gas used:            261393
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00522786 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00522786 ETH


2_deploy_asset.js
=================

   Replacing 'Asset'
   -----------------
   > transaction hash:    0xad054c38523eb8e8f181d3c7f777f3d1e47da018e82a4eaf492546a951dec239
   > Blocks: 0            Seconds: 0
   > contract address:    0x7c107079Aaa4cb108685a3413f604f86f1d9f554
   > block number:        31
   > block timestamp:     1563933880
   > account:             0xf7654E6fC8Ca3e7dB6C615D49C3e184A8A39E3DB
   > balance:             99.48850862
   > gas used:            2869784
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.05739568 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.05739568 ETH


Summary
=======
> Total deployments:   2
> Final cost:          0.06262354 ETH

これはいわゆるレシートですね。今回のトランザクションハッシュはこれで、コントラクトアドレスはこれですよ。デプロイにいくらのgas がかかって、いくらのgas peiceでやって、トータルでこれだけのコストが掛かりましたよなどですね。

Metamskで確認

早速作ったNFTをMetamaskで見てみます。ネットワークはGanchaと接続した状態にしておきます。 左のメニューバーを開いて下にある「トークンを追加」を押す。その後トークンを追加という画面になるので、カスタムトークンを選択し、「Token Contract Address」に先ほどデプロイ時のレシート的なものに出ていたContract Address の値(2_deploy_asset.js の方)を入れて「次へ」を押す。 f:id:mochi-mochi-0397:20190803103128p:plain すると自動で該当のトークン表 示されるので、「トークンを追加」を押すとMetamaskに追加されている。

f:id:mochi-mochi-0397:20190803103234p:plain

追加発行

今回はデプロイと同時に発行を行ったが、せっかく追加発行できる関数を入れたので、それを使ってみる。 ちなみに使えるコマンド等はtab キー二回連打で以下のように表示される。

Array                         Boolean                       Date
Error                         EvalError                     Function
Infinity                      JSON                          Math
NaN                           Number                        Object
RangeError                    ReferenceError                RegExp
String                        SyntaxError                   TypeError
URIError                      decodeURI                     decodeURIComponent
encodeURI                     encodeURIComponent            eval
isFinite                      isNaN                         parseFloat
parseInt                      undefined                     

Address                       ArrayBuffer                   Asset
Atomics                       BigInt                        BigInt64Array
BigUint64Array                Buffer                        Counters
DTRACE_HTTP_CLIENT_REQUEST    DTRACE_HTTP_CLIENT_RESPONSE   DTRACE_HTTP_SERVER_REQUEST
DTRACE_HTTP_SERVER_RESPONSE   DTRACE_NET_SERVER_CONNECTION  DTRACE_NET_STREAM_END
DataView                      ERC165                        ERC721
ERC721Enumerable              ERC721Full                    ERC721Metadata
Float32Array                  Float64Array                  GLOBAL
IERC165                       IERC721                       IERC721Enumerable
IERC721Metadata               IERC721Receiver               Int16Array
Int32Array                    Int8Array                     Intl
Map                           Migrations                    Promise
Proxy                         Reflect                       SafeMath
Set                           SharedArrayBuffer             Symbol
URL                           URLSearchParams               Uint16Array
Uint32Array                   Uint8Array                    Uint8ClampedArray
WeakMap                       WeakSet                       WebAssembly
_                             _error                        assert
async_hooks                   buffer                        child_process
clearImmediate                clearInterval                 clearTimeout
cluster                       console                       crypto
dgram                         dns                           domain
escape                        events                        fs
global                        http                          http2
https                         inspector                     module
net                           os                            path
perf_hooks                    process                       punycode
querystring                   readline                      repl
require                       root                          setImmediate
setInterval                   setTimeout                    stream
string_decoder                tls                           trace_events
tty                           unescape                      url
util                          v8                            vm
web3                          zlib                          

__defineGetter__              __defineSetter__              __lookupGetter__
__lookupSetter__              __proto__                     constructor
hasOwnProperty                isPrototypeOf                 propertyIsEnumerable
toLocaleString                toString                      valueOf

この中でも一番大事なのがweb3というもの。これはEthereum JavaScript APIで、ローカル・リモートのEthereumノードとやり取りできるJavascriptライブラリのこと。実際にデプロイしたコントラクトとのやり取りはこいつを使う場合が多い。なので仲良くなっておきましょう。詳細はgithubを見てください。

早速発行しようと行きたいところですが、まずは自分の残高を確認ターミナル上で確認しておきましょう。まず以下のコマンドでアカウントを取得します。

 truffle(development)> accounts = await web3.eth.getAccounts()

ここで大事な点はawaitです。古いweb3バージョンだとweb3.eth.getAccounts() だけでも良いらしいのですが、最新バージョンだとこいつが使えます。JSの教養が乏しいので詳しくは説明できませんが、awaitなしだとPromise型で値が返ってくるのでこいつで何とかしないといけない。(よくわかっていなけどasync/await 入門(JavaScript)を読めば良いと思う) 例えばawait有り無しで作った時の型の違いを見てみる。

truffle(development)> accounts = web3.eth.getAccounts()
undefined
truffle(develop)> accounts
[ '0xf7654E6fC8Ca3e7dB6C615D49C3e184A8A39E3DB',
  '0x6a2951c7cD868e43936e401B4F1b956Ea07DFD08',
  '0xff380d7114c2B95872b0D953c4111c44Cd21a2dF',
  '0xC0C53371A19A1bc7C97A4212A248A3995D3dEfBc',
  '0xDdABA55D62658477483ECE4352803804048A45E7',
  '0xFfBa02dC6ff91e4AbEEEE8Bf8043524f84FC6bFb',
  '0x855221E7d5522e1Af6Cabf33Ba22027C727d6AE0',
  '0x01200E489974A2Ad5B7e96dae3c770f73371baCA',
  '0xaE788e6fdD86AF932882FDcF50963003b18f0655',
  '0x05f9FA1707CbB3dE4f1A7DE5d6A5AA66bEb203e0' ]
  
 truffle(development)> accounts.toString()
'[object Object]'

truffle(development)> accounts1 = web3.eth.getAccounts()
[ '0xf7654E6fC8Ca3e7dB6C615D49C3e184A8A39E3DB',
  '0x6a2951c7cD868e43936e401B4F1b956Ea07DFD08',
  '0xff380d7114c2B95872b0D953c4111c44Cd21a2dF',
  '0xC0C53371A19A1bc7C97A4212A248A3995D3dEfBc',
  '0xDdABA55D62658477483ECE4352803804048A45E7',
  '0xFfBa02dC6ff91e4AbEEEE8Bf8043524f84FC6bFb',
  '0x855221E7d5522e1Af6Cabf33Ba22027C727d6AE0',
  '0x01200E489974A2Ad5B7e96dae3c770f73371baCA',
  '0xaE788e6fdD86AF932882FDcF50963003b18f0655',
  '0x05f9FA1707CbB3dE4f1A7DE5d6A5AA66bEb203e0' ]
  
 truffle(development)> accounts1.toString()
'[object Promise]'

となる。最初undefined と返ってくるので焦ったけど、ちゃんと入っている。実際にmint関数を動かすためにはPromise型では都合が悪いので、忘れずawait をします。

一旦instanceというコントラクトオブジェクト(ETHネットワークを通信するオブジェクト)を作成し、アカウントの0番目の量を見てみます。 今回はデプロイしたらNFTが発行されるので、先頭のアカウントが発行したため1つ保持しているはずです。

truffle(development)> instance = await Asset.deployed()
undefined
truffle(development)> instance.balanceOf(account[0])
<BN: 1>
truffle(development)> instance.mint(0)
{ tx:
   '0x6f3caf87af645fdb2051a376cb7389e2b2ffb3a2677ff50ee042a1a364c0ef80',
  receipt:
   { transactionHash:
      '0x6f3caf87af645fdb2051a376cb7389e2b2ffb3a2677ff50ee042a1a364c0ef80',
     transactionIndex: 0,
     blockHash:
      '0x42563adf041c53598b8a5b6788e32f9746d914e5dead6edf2c7fb93084f93954',
     blockNumber: 39,
     from: '0xf7654e6fc8ca3e7db6c615d49c3e184a8a39e3db',
     to: '0x7c107079aaa4cb108685a3413f604f86f1d9f554',
     gasUsed: 111297,
     cumulativeGasUsed: 111297,
     contractAddress: null,
     logs: [ [Object] ],
     status: true,
     logsBloom:

  ・・・・途中省略・・・
       
       '0x42563adf041c53598b8a5b6788e32f9746d914e5dead6edf2c7fb93084f93954',
       blockNumber: 39,
       address: '0x7c107079Aaa4cb108685a3413f604f86f1d9f554',
       type: 'mined',
       id: 'log_9ba02281',
       event: 'Transfer',
       args: [Result] } ] }

無事できたら残高が増えているか確認してみます。

truffle(development)> instance.balanceOf(account[0])
<BN: 2>

ちゃんと増えているので成功ですね。もちろんMetamaskでも同じように確認できるはず。

送信

作ったはいいけど、このNFTは誰にも送信できません。ということで、送信できるようにしてみましょう。

以下完成コードです。

 pragma solidity ^0.5.0;

import "../node_modules/openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol";

contract Asset is ERC721Full {
    constructor(
        string memory name,
        string memory symbol,
        uint tokenId,
        string memory tokenURI
    )

    ERC721Full(name, symbol) public {
        _mint(msg.sender, tokenId);
        _setTokenURI(tokenId, tokenURI);
    }

    function mint(uint256 tokenId) public{
        _mint(msg.sender, tokenId);
    }

    function transfer(address to, uint tokenId) public {
        _transferFrom(msg.sender, to, tokenId);
    }
}

変更点は最後のtransfer関数です。これはERC721.sol内にある_transferFrom(msg.sender, to, tokenId);を呼び出します。引数には送信先のアドレスとtokenIDを渡します。送り元はコントラクトを呼び出したアドレスになります。

では早速送信してみましょう。先ほどまでで下準備はできているので、単純にtransfer関数を読んで送信先TokenIDを指定するだけです。

truffle(develop)> instance.transfer(account[1], 0)
{ tx:
   '0xfb67245af52b2762ddd6fe7b142bf086a08db860b06c21a763ea38089e359962',
  receipt:
   { transactionHash:
      '0xfb67245af52b2762ddd6fe7b142bf086a08db860b06c21a763ea38089e359962',
     transactionIndex: 0,
     blockHash:
      '0x119fa44f6ac8374e94ded70ca2e1a14cfbfed6be14483cea85e829d33c3868cf',
     blockNumber: 6,
     from: '0x752a8c5261321fe0a9f1878e120149bac533b212',
     to: '0x75f3e2e19acecb1867a7e025530bd2224383137b',
     gasUsed: 84538,
     cumulativeGasUsed: 84538,
     contractAddress: null,
     logs: [ [Object] ],
     status: true,
     logsBloom:
      '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000040000000000000000000000000000000000001000000008000000000000000000000000000000000000000000000000020000000000000000000800000020000000000000000010000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000002000000000000000000000000000000000000000000000040000020100000000000000000000000000000000000000000000000000000000000100000',
     v: '0x1b',
     r:
      '0x70690692409a293b16d9b26fe09907c866143b8f817b858c9ad7d57ec1583bb4',
     s:
      '0x45de03a717b20f2715fcb8caa6b4b1c246f11a1006be7a8e3ee5a1da35635bba',
     rawLogs: [ [Object] ] },
  logs:
   [ { logIndex: 0,
       transactionIndex: 0,
       transactionHash:
        '0xfb67245af52b2762ddd6fe7b142bf086a08db860b06c21a763ea38089e359962',
       blockHash:
        '0x119fa44f6ac8374e94ded70ca2e1a14cfbfed6be14483cea85e829d33c3868cf',
       blockNumber: 6,
       address: '0x75F3e2E19aCecb1867a7E025530BD2224383137B',
       type: 'mined',
       id: 'log_e2acc8ac',
       event: 'Transfer',
       args: [Result] } ] }
truffle(develop)> instance.balanceOf(account[0])
<BN: 1>
truffle(develop)> instance.balanceOf(account[1])
<BN: 1>

これでオリジナルトークンの発行と送信ができるようになりました。めでたしめでたし。

まとめ

内容は単純だけど、いざ自分で調べてやってみるとしょうもないところで躓いてなかなか思うように進まない。

P.S. ところでPromiseってなんです???

参考文献

Truffleチュートリアル

前置き

Ethereumのwebフレームワークとして代表的なTruffle(トリュフ)のチュートリアルをやっていく。基本的に原文を読めばいいのだが、途中つまづいたりしたところを載せながら、不必要な部分を削除しながら進める。英語と翻訳を載せている部分は、google先生と自分の意訳が9:1ぐらいの割合になっています。なので正確な情報を知りたい方はソース元を見てください。

尚株式会社Chaintopeという所にはMr NakajoというリアルガチTruffleコントリビューターがいるので、素早く解決したい&コネがある方は彼に聞いた方が早いと思われる。

環境

Mac

Truffleの概要

Ethereumのスマートコントラクトを書くためのフレームワーク (Ruby on Rail的な感じ)。おそらく現状のベストプラクティス。というかこれしかないっぽい。 他、ライブラリなどの開発環境に関しては少し古いけど以下の記事がわかりやすいのでそれを参考にすると良いと思う。 - EthereumとContracts開発を取り巻くエコシステムの概要

背景

Pete Scandlon of Pete's Pet Shop is interested in using Ethereum as an efficient way to handle their pet adoptions. The store has space for 16 pets at a given time, and they already have a database of pets. As an initial proof of concept, Pete wants to see a dapp which associates an Ethereum address with a pet to be adopted.

The website structure and styling will be supplied. Our job is to write the smart contract and front-end logic for its usage.

Pete's ペットショップを経営するPete Scandlonは、効率的にペットの飼い主決定を処理する方法として、Ethereumを使ってみたいと考えています。この店にはペット用のスペースが16匹分有り、またすでにペットのデータベースは持っています。オーナーのPeteはまず第一歩として、コンセプトを理解するために、ペットとEthereumのアドレスを紐付けるようなdappを見てみたいと考えています。 これを動かすためのwebサイトの構造とスタイリングは提供されます。私達の仕事は、スマートコントラクトと、それを使用するためのフロントエンドのロジックを作成することです。

環境設定

初める前に以下が必要。ない場合は以下のリンク先に従って設定しよう。

Truffleインストール

npm install -g truffle

これだけ、あら簡単。すでにNode.jsが入っている人は一旦$ npm install -g npmとやっておくと良いかも。ちなみに自分は前にTruffleを入れていたので、$ npm install -g npmの後に $ npm update -g truffleでバージョンアップさせた。

終わったら$ truffle version, $ which truffleとか打ってバージョン,パスが出てきたらOK。自分の環境下では以下のようになった。

バージョン:Truffle v5.0.26 - a development framework for Ethereum

パス:/Users/toshiki/.nvm/versions/node/v10.13.0/bin/truffle

プロジェクト作成

Truffleは現在のディレクトリで初期化するので、最初に選択した開発フォルダにディレクトリを作成してから、その中に移動する。

$ mkdir -p pet-shop-tutorial; cd pet-shop-tutorial
$ truffle unbox pet-shop

ちなみに初期化としては$ truffle initもある。こちらはコントラクトのサンプルを含まないらしいので、それらが必要ない方はinitを使用すれば良いかと。

ディレクトリ構造

The default Truffle directory structure contains the following:

  • contracts/: Contains the Solidity source files for our smart contracts. There is an important contract in here called Migrations.sol, which we'll talk about later.
  • migrations/: Truffle uses a migration system to handle smart contract deployments. A migration is an additional special smart contract that keeps track of changes.
  • test/: Contains both JavaScript and Solidity tests for our smart contracts
  • truffle-config.js: Truffle configuration file

The pet-shop Truffle Box has extra files and folders in it, but we won't worry about those just yet.

デフォルトのTruffleディレクトリ構造には、次のものが含まれています。 - contracts/:スマートコントラクト用のSolidityソースファイルが含まれています。ここには重要な契約がありMigrations.solます。これについては後で説明します。 - migrations/:Truffleは、スマート契約の導入を処理するために移行システムを使用しています。移行は、変更を追跡する追加の特別なスマート契約です。 - test/:スマートコントラクトのためのJavaScriptテストとSolidityテストの両方が含まれています - truffle-config.js:トリュフ設定ファイル

pet-shopトリュフボックスがそれに余分なファイルやフォルダを持っているが、我々はまだ、これらの心配はありません

スマートコントラクトを書く

  1. contracts/ディレクトリにAdoption.solを作成
  2. ファイルには以下を書く
pragma solidity ^0.5.0;

contract Adoption {

}

解説

必要なSolidityの最小バージョンは、コントラクトのトップに記載されている0.5.0。pragmaコマンドは「 コンパイラのみが気にする追加情報 」を意味し、キャレット記号(^)は「 それ以上のバージョン 」を意味します。 JavaScriptPHPと同様に、ステートメントセミコロンで終了します。

変数設定

Solidity is a statically-typed language, meaning data types like strings, integers, and arrays must be defined. Solidity has a unique type called an address. Addresses are Ethereum addresses, stored as 20 byte values. Every account and smart contract on the Ethereum blockchain has an address and can send and receive Ether to and from this address.

Solidityは静的プログラミング言語です。なので、文字列、整数、配列などのデータ型を定義しなければなりません。またSolidityにはアドレスと呼ばれるunique typeがあります 。アドレスは、20バイトの値として格納されたEthereumアドレスです。Ethereumブロックチェーンのすべてのアカウントとスマートコントラクトにはアドレスがあり、このアドレスとの間でEtherを送受信できます。

  1. contract Adoption { 後の次の行に次の変数を追加。
address[16] public adopters;

Things to notice:

  • We've defined a single variable: adopters. This is an array of Ethereum addresses. Arrays contain one type and can have a fixed or variable length. In this case the type is address and the length is 16.
  • You'll also notice adopters is public. Public variables have automatic getter methods, but in the case of arrays a key is required and will only return a single value. Later, we'll write a function to return the whole array for use in our UI.

解説:

  • 変数adoptersを定義しました。これはEthereumアドレスの配列です。配列には1つの型が含まれ、固定長または可変長を持つことができます。この場合、タイプはaddressで長さは16です。
  • また、adoptersがpublicなことに気づくでしょう。public変数には自動ゲッターメソッドがありますが、配列の場合はキーが必要であり、単一の値しか返しません。後で、UI全体で使用するために配列全体を返す関数を作成します。

1つ目のFunction : ペットの引取り

お客様がペットの引取を要求できるようにしよう。 1. 上記で設定した変数宣言のあとに、次の関数を追加。

// Adopting a pet
function adopt(uint petId) public returns (uint) {
  require(petId >= 0 && petId <= 15);

  adopters[petId] = msg.sender;

  return petId;
}

Things to notice:

  • In Solidity the types of both the function parameters and output must be specified. In this case we'll be taking in a petId (integer) and returning an integer.

  • We are checking to make sure petId is in range of our adopters array. Arrays in Solidity are indexed from 0, so the ID value will need to be between 0 and 15. We use the require() statement to ensure the ID is within range.

  • If the ID is in range, we then add the address that made the call to our adopters array. The address of the person or smart contract who called this function is denoted by msg.sender.

  • Finally, we return the petId provided as a confirmation.

解説:

  • Solidityでは、関数のパラメータと出力の両方の型を指定する必要があります。ここでは、パラメータとしてpetId(integer)を入力し、integerを出力する設定です。
  • petIdadopters配列の範囲内にあることを確認しています。Solinityの配列は0からインデックスされるため、ID値は0から15の間である必要があります。IDが範囲内にあることを確認するには、 require()ステートメントを使用します。
  • IDが範囲内にある場合は、呼び出しを行ったアドレスをadopters配列に追加します。この機能を呼び出した人、または呼び出したスマートコントラクトのアドレスは、 msg.senderによって示されます。
  • 最後に、提供された petId を確認のために返します。

2つ目のFunction : Adoptersの取得

As mentioned above, array getters return only a single value from a given key. Our UI needs to update all pet adoption statuses, but making 16 API calls is not ideal. So our next step is to write a function to return the entire array.

前述のように、配列ゲッターは与えられたキーから単一の値だけを返します。私たちのUIはすべてのペットの採用状況を更新する必要がありますが、16のAPI呼び出しを行うことは理想的ではありません。だから次のステップは、配列全体を返す関数を書くことです。

(1) 上に追加したgetAdopters()関数の後に、次のadopt()関数をスマートコントラクトに追加します。

// Retrieving the adopters
function getAdopters() public view returns (address[16]) {
  return adopters;
}

(2) Since adopters is already declared, we can simply return it. Be sure to specify the return type (in this case, the type for adopters) as address[16] memory. memory gives the data location for the variable.

(3) The view keyword in the function declaration means that the function will not modify the state of the contract. Further information about the exact limits imposed by view is available here.

(2) adoptersは既に宣言されているので、単にそれを返すことができます。必ず戻り型(この場合はforの型adopters)を指定してくださいaddress[16] memorymemory変数のデータ位置を指定します。
(3) view関数宣言内のキーワードは、その関数が契約の状態を変更しないことを意味します。ビューによって課される正確な制限についてのさらなる情報はここで利用可能です。

スマートコントラクトのコンパイルと移行

スマートコントラクトが作成出来たので、次のステップは、それをコンパイルして移行することです。 Truffleには開発者コンソールが組み込まれています。これをTruffle Developと呼びます。これはデプロイしたスマートコントラクトをテストするために使用できる、開発ブロックチェーンを生成します。 また、コンソールからTruffleコマンドを直接実行することもできます。このチュートリアルでは、Truffle Developを使用して、スマートコントラクトのほとんどの処理を実行します。

あら簡単!これで今回作りたいスマートコントラクトは完成 以下に今回の全体コード載せておきます。(2019/7/9時点)

pragma solidity ^0.5.0;
contract Adoption {
  address[16] public adopters;

  function adopt(uint petId) public returns (uint) {
  require(petId >= 0 && petId <= 15);

  adopters[petId] = msg.sender;

  return petId;
  }

  function getAdopters() public view returns (address[16] memory) {
    return adopters;
  }
}

コンパイル

Solidity is a compiled language, meaning we need to compile our Solidity to bytecode for the Ethereum Virtual Machine (EVM) to execute. Think of it as translating our human-readable Solidity into something the EVM understands.

Solidityはコンパイルされた言語です。つまり、Ethereum仮想マシン(EVM)を実行するためにSolinityをバイトコードコンパイルする必要があります。人間が読めるSolidityをEVMが理解できるものに変換するものと考えてください。

  1. 端末で、dappを含むディレクトリのルートにあることを確認して、次のように入力します。 自分の場合は以下の通り(おそらく手順通りやっていればこうなっているはず) f:id:mochi-mochi-0397:20190709171950p:plain

$ truffle compile

f:id:mochi-mochi-0397:20190709172032p:plain

Migration

Now that we've successfully compiled our contracts, it's time to migrate them to the blockchain!

A migration is a deployment script meant to alter the state of your application's contracts, moving it from one state to the next. For the first migration, you might just be deploying new code, but over time, other migrations might move data around or replace a contract with a new one.

スマートコントラクトが完成したので、ブロックチェーンに移行しましょう! Migrationとは、アプリケーションの契約の状態を変更し、ある状態から次の状態に移行するためのデプロイスクリプトです。 最初の移行では、新しいコードを導入しているだけかもしれませんが、他の場合にはデータを移動したり、契約を新しいものに置き換えたりする可能性があります。

注意:移行の詳細については、Truffleのドキュメントを参照してください

1つのJavaScriptファイルがmigrations/ディレクトリにあることが確認できるでしょう(1_initial_migration.js)。 これは、Migrations.solコントラクトを展開し、その後のスマートコントラクトの移行を監視し、二重契約が起きないようにハンドルします。 これで、独自の移行スクリプトを作成する準備が整いました。

(1)migrations/下に2_deploy_contracts.jsという名前の新しいファイルを作成します。

(2)2_deploy_contracts.jsファイルに次のコードを追加します。

var Adoption = artifacts.require("Adoption");

module.exports = function(deployer) {
  deployer.deploy(Adoption);
};

(3)コントラクトをブロックチェーンに移行する前に、ブロックチェーンを実行する必要があります。このチュートリアルでは、契約の展開、アプリケーションの開発、テストの実行に使用できるEthereum開発用の個人用ブロックチェーンであるGanacheを使用します。 Ganacheをまだダウンロードしていない場合は、 Ganacheをダウンロードしてアイコンをダブルクリックしてアプリケーションを起動してください。これにより、ポート7545でローカルで動作するブロックチェーンが生成されます。(水色部分がニーモニックになっているので、忘れないように!後で使うよ) f:id:mochi-mochi-0397:20190709180056p:plain

(4)ターミナルに戻って、コントラクトをブロックチェーンに移行します。

 $ truffle migrate

すると、次のような出力が表示されます。 f:id:mochi-mochi-0397:20190709181141p:plain

順番に移行が実行され、展開されたコントラクトのブロックチェーンアドレスが表示されます(アドレスは個人毎に異なります)。

  1. Ganacheでは、ブロックチェーンの状態が変わったことに注意してください。CURRENT BLOCKは0から4に。最初のアカウントはもともと100Etherを持っていましたが、移行のトランザクションコストのために今は低くなっています。後で取引費用について詳しく説明します。f:id:mochi-mochi-0397:20190709180636p:plain

最初のスマートコントラクトを書いて、それをローカルで実行しているブロックチェーンに配備しました。 私たちが望んでいることを確かめるために、スマートコントラクトとやり取りする準備が整いました。

Test

Truffle is very flexible when it comes to smart contract testing, in that tests can be written either in JavaScript or Solidity. In this tutorial, we'll be writing our tests in Solidity.

Create a new file named TestAdoption.sol in the test/ directory.

Add the following content to the TestAdoption.sol file:

スマートコントラクトのテストに関しては、Truffleは非常に柔軟性があります。テストはJavaScriptまたはSolidityで書くことができるからです。このチュートリアルでは、Solidityでテストを書きます。
(1)名前の新しいファイルをtest/ディレクトリ下にTestAdoption.solを作成します。 (2)TestAdoption.solに次の内容を追加します。

pragma solidity ^0.5.0;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";

contract TestAdoption {
 // The address of the adoption contract to be tested
 Adoption adoption = Adoption(DeployedAddresses.Adoption());

 // The id of the pet that will be used for testing
 uint expectedPetId = 8;

 //The expected owner of adopted pet is this contract
 address expectedAdopter = address(this);

}

We start the contract off with 3 imports:

  • Assert.sol: Gives us various assertions to use in our tests. In testing, an assertion checks for things like equality, inequality or emptiness to return a pass/fail from our test. Here's a full list of the assertions included with Truffle.
  • DeployedAddresses.sol: When running tests, Truffle will deploy a fresh instance of the contract being tested to the blockchain. This smart contract gets the address of the deployed contract.
  • Adoption.sol: The smart contract we want to test.

Note: The first two imports are referring to global Truffle files, not a truffle directory. You should not see a truffle directory inside your test/ directory.

Then we define three contract-wide variables:

  • First, one containing the smart contract to be tested, calling the DeployedAddresses smart contract to get its address.
  • Second, the id of the pet that will be used to test the adoption functions.
  • Third, since the TestAdoption contract will be sending the transaction, we set the expected adopter address to this, a contract-wide variable that gets the current contract's address.

我々は3つのimportでスマコンを始めます: - Assert.sol:私たちのテストで使用するためのさまざまなアサーションを提供します。テストでは、アサーションはテストの合否を返すための平等、不平等、または空などをチェックします。これがTruffleに含まれるアサーションの全リストです。 - DeployedAddresses.soを実行すると、Truffleはテスト中の契約の新しいインスタンスブロックチェーンにデプロイします。このスマートコントラクトは、デプロイされたコントラクトのアドレスを取得します。 - Adoption.sol:テストしたいスマートコントラクト
注意:最初の2つのインポートは、truffleディレクトリではなく、グローバルなTruffleファイルを参照しています。あなたはあなたのtruffleディレクトリの中にtest/ディレクトリを見るべきではありません。

次に、3つのスマコンの変数を定義します。

  • まず、テスト対象のスマートコントラクトを含み、DeployedAddressesそのアドレスを取得するためにスマートコントラクトを呼び出します。
  • 次に、養子縁組機能のテストに使用されるペットのID。
  • 3つ目は、TestAdoption契約がトランザクションを送信するため、現在の契約の住所を取得する契約全体の変数である、予想される採用者の住所をこれに設定します。

Testing the adopt() function

To test the adopt() function, recall that upon success it returns the given petId. We can ensure an ID was returned and that it's correct by comparing the return value of adopt() to the ID we passed in.

(1)Add the following function within the TestAdoption.sol smart contract, after the declaration of Adoption:

// Testing the adopt() function
function testUserCanAdoptPet() public {
  uint returnedId = adoption.adopt(expectedPetId);

  Assert.equal(returnedId, expectedPetId, "Adoption of the expected pet should match what is returned.");
}

Things to notice:

  • We call the smart contract we declared earlier with the ID of expectedPetId.
  • Finally, we pass the actual value, the expected value and a failure message (which gets printed to the console if the test does not pass) to Assert.equal().

解説

先に宣言したスマートコントラクトをのIDと呼びますexpectedPetId。 最後に、実際の値、期待値、および失敗メッセージ(テストに合格しなかった場合はコンソールに表示される)を渡しAssert.equal()ます。

Testing retrieval of a single pet's owner

Remembering from above that public variables have automatic getter methods, we can retrieve the address stored by our adoption test above. Stored data will persist for the duration of our tests, so our adoption of pet expectedPetId above can be retrieved by other tests.

上記から、パブリック変数には自動取得メソッドがあることを思い出してください。上記の採用テストで保存されたアドレスを取得できます。保存されたデータは私達のテストの間持続するので、expectedPetId上のペットの私達の採用は他のテストで検索することができます。

(1)先ほど追加した関数の下に以下の関数を追加してください

// Testing retrieval of a single pet's owner
function testGetAdopterAddressByPetId() public {
  address adopter = adoption.adopters(expectedPetId);

  Assert.equal(adopter, expectedAdopter, "Owner of the expected pet should be this contract");
}

After getting the adopter address stored by the adoption contract, we assert equality as we did above.

採用契約によって採用された採用者の住所を取得した後、上記と同じように平等を主張します。

Testing retrieval of all pet owners

Since arrays can only return a single value given a single key, we create our own getter for the entire array.

配列は単一のキーで与えられた単一の値しか返すことができないので、配列全体に対して独自のゲッターを作成します。

(1)先ほど追加した関数の下に以下の関数を追加してください

// Testing retrieval of all pet owners
function testGetAdopterAddressByPetIdInArray() public {
  // Store adopters in memory rather than contract's storage
  address[16] memory adopters = adoption.getAdopters();

  Assert.equal(adopters[expectedPetId], expectedAdopter, "Owner of the expected pet should be this contract");
}

Note the memory attribute on adopters. The memory attribute tells Solidity to temporarily store the value in memory, rather than saving it to the contract's storage. Since adopters is an array, and we know from the first adoption test that we adopted pet expectedPetId, we compare the testing contracts address with location expectedPetId in the array.

adoptersのメモリ属性に注意してください。memory属性は、値を契約のストレージに保存するのではなく、一時的にメモリに保存するようにSolidityに指示します。以降のadopters配列であり、そして我々はペットを採用した最初の採用試験から知っているexpectedPetId、我々は、テスト契約が位置アドレス比較expectedPetId配列内。

完成コードは以下の通り(2019/7/9時点)

pragma solidity ^0.5.0;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";

contract TestAdoption {
  Adoption adoption = Adoption(DeployedAddresses.Adoption());

  uint expectedPetId = 8;

  address expectedAdopter = address(this);

  function testUserCanAdoptPet() public {
    uint returnedId = adoption.adopt(8);

    Assert.equal(returnedId, expectedPetId, "Adoption of the expected pet should match what is recorded.");
  }

  function testGetAdopterAddressByPetId() public {
    address adopter = adoption.adopters(expectedPetId);

    Assert.equal(adopter, expectedAdopter, "Owner of the expected pet should be this contract");
  }

  function testGetAdopterAddressByPetIdArray() public {
    address[16] memory adopters = adoption.getAdopters();

    Assert.equal(adopters[expectedPetId], expectedAdopter, "Owner of the expected pet should be this contract");
  }
}

テスト実行

(1) ターミナルに戻って、テストを実行しましょう。コマンドは以下の通り

$ truffle test

(2) もし成功したら以下のようになります。(見辛くてごめんなさい!ここは本家を見た方がいいかも) f:id:mochi-mochi-0397:20190709173145p:plain

コントラクトを使用するためのインターフェイスの作成

Now that we've created the smart contract, deployed it to our local test blockchain and confirmed we can interact with it via the console, it's time to create a UI so that Pete has something to use for his pet shop!

Included with the pet-shop Truffle Box was code for the app's front-end. That code exists within the src/ directory.

The front-end doesn't use a build system (webpack, grunt, etc.) to be as easy as possible to get started. The structure of the app is already there; we'll be filling in the functions which are unique to Ethereum. This way, you can take this knowledge and apply it to your own front-end development.

スマートコントラクトを作成し、それをローカルのテストブロックチェーンにデプロイし、コンソールを介して対話できることを確認したので、Peteが自分のペットショップに使用できるようにUIを作成します。pet-shopトリュフボックスには、アプリのフロントエンド用のコードが含まれています。そのコードはsrc/ディレクトリ内に存在します。 できるだけ簡単に始めるために、フロントエンドはビルドシステム(webpack、gruntなど)を使用しません。アプリの構造はすでにあります。私達はEthereumに特有の機能を埋めます。このようにして、あなたはこの知識を取り、あなた自身のフロントエンド開発にそれを適用することができます。

Instantiating web3

(1) /src/js/app.js を適当なエディターで開きます。

(2) ファイルを調べてください。Appアプリケーションを管理し、petデータをロードしてinit()から関数を呼び出すためのグローバルオブジェクトがありますinitWeb3()。JavaScriptのライブラリweb3イーサリアムのblockchainと対話します。ユーザーアカウントの取得、トランザクションの送信、スマートコントラクトとのやり取りなどができます。

(3) initWeb3の中のコメントを削除し、以下のコードを入力します。

// Modern dapp browsers...
if (window.ethereum) {
  App.web3Provider = window.ethereum;
  try {
    // Request account access
    await window.ethereum.enable();
  } catch (error) {
    // User denied account access...
    console.error("User denied account access")
  }
}
// Legacy dapp browsers...
else if (window.web3) {
  App.web3Provider = window.web3.currentProvider;
}
// If no injected web3 instance is detected, fall back to Ganache
else {
  App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
}
web3 = new Web3(App.web3Provider);

Things to notice:

  • First, we check if we are using modern dapp browsers or the more recent versions of MetaMask where an ethereum provider is injected into the window object. If so, we use it to create our web3 object, but we also need to explicitly request access to the accounts with ethereum.enable().

  • If the ethereum object does not exist, we then check for an injected web3 instance. If it exists, this indicates that we are using an older dapp browser (like Mist or an older version of MetaMask). If so, we get its provider and use it to create our web3 object.

  • If no injected web3 instance is present, we create our web3 object based on our local provider. (This fallback is fine for development environments, but insecure and not suitable for production.)

解説

  • 私たちは、現代DAPPブラウザまたはより新しいバージョンの使用している場合はまず、我々はチェックMetaMaskethereumプロバイダが中に注入されるwindowオブジェクトを。もしそうなら、私達は私達のweb3オブジェクトを作成するためにそれを使用しますが、我々はまた明示的にアカウントへのアクセスを要求する必要がありますethereum.enable()
  • ethereumオブジェクトが存在しない場合は、挿入されたweb3インスタンスを確認します。もしそれが存在すれば、これは私たちがより古いdappブラウザ(MistやMetaMaskのより古いバージョンのような)を使っていることを示しています。もしそうなら、そのプロバイダを取得し、それを使ってweb3オブジェクトを作成します。
  • 注入されたweb3インスタンスが存在しない場合は、ローカルプロバイダに基づいてweb3オブジェクトを作成します。(このフォールバックは開発環境には問題ありませんが、安全ではなく、本番環境には適していません。)

Instantiating the contract

Now that we can interact with Ethereum via web3, we need to instantiate our smart contract so web3 knows where to find it and how it works. Truffle has a library to help with this called truffle-contract. It keeps information about the contract in sync with migrations, so you don't need to change the contract's deployed address manually.

web3を介してEthereumとやり取りできるようになったので、web3がそれを見つける場所と動作方法を認識できるようにスマートコントラクトをインスタンス化する必要があります。Truffleにはこれを手助けするライブラリがありますtruffle-contract。契約に関する情報を移行と同期させるので、契約の展開先アドレスを手動で変更する必要はありません。

(1) 先ほどのコードの下にある、 initContractの中のコメントを削除し、以下のコードを入力します。

$.getJSON('Adoption.json', function(data) {
  // Get the necessary contract artifact file and instantiate it with truffle-contract
  var AdoptionArtifact = data;
  App.contracts.Adoption = TruffleContract(AdoptionArtifact);

  // Set the provider for our contract
  App.contracts.Adoption.setProvider(App.web3Provider);

  // Use our contract to retrieve and mark the adopted pets
  return App.markAdopted();
});

Things to notice:

  • We first retrieve the artifact file for our smart contract. Artifacts are information about our contract such as its deployed address and Application Binary Interface (ABI). The ABI is a JavaScript object defining how to interact with the contract including its variables, functions and their parameters.

  • Once we have the artifacts in our callback, we pass them to TruffleContract(). This creates an instance of the contract we can interact with.

  • With our contract instantiated, we set its web3 provider using the App.web3Provider value we stored earlier when setting up web3.

  • We then call the app's markAdopted() function in case any pets are already adopted from a previous visit. We've encapsulated this in a separate function since we'll need to update the UI any time we make a change to the smart contract's data.

解説:

  • 最初に、スマートコントラクトの案件ファイルを取得します。成果物とは、デプロイされたアドレスやApplication Binary Interface(ABI)など、契約に関する情報です。ABIは、その変数、関数、およびそれらのパラメータを含む、コントラクトとの対話方法を定義するJavaScriptオブジェクトです。

  • コールバックにアーティファクトがあると、それをに渡しますTruffleContract()。これにより、私たちがやり取りできる契約のインスタンスが作成されます。

  • インスタンス化された私達の契約で、私達はweb3をセットアップするApp.web3Providerときに先に保存した値を使ってそのweb3プロバイダーを設定します。

  • markAdopted() 前回の訪問でペットが既に養子になっている場合に備えて、アプリの機能を呼び出します。スマートコントラクトのデータを変更するたびにUIを更新する必要があるため、これを別の関数にカプセル化しました。

Getting The Adopted Pets and Updating The UI

(1) markAdoptedの中のコメントを削除し、以下のコードを入力します。

var adoptionInstance;

App.contracts.Adoption.deployed().then(function(instance) {
  adoptionInstance = instance;

  return adoptionInstance.getAdopters.call();
}).then(function(adopters) {
  for (i = 0; i < adopters.length; i++) {
    if (adopters[i] !== '0x0000000000000000000000000000000000000000') {
      $('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);
    }
  }
}).catch(function(err) {
  console.log(err.message);
});

Things to notice:

  • We access the deployed Adoption contract, then call getAdopters() on that instance.

  • We first declare the variable adoptionInstance outside of the smart contract calls so we can access the instance after initially retrieving it.

  • Using call() allows us to read data from the blockchain without having to send a full transaction, meaning we won't have to spend any ether.

  • After calling getAdopters(), we then loop through all of them, checking to see if an address is stored for each pet. Since the array contains address types, Ethereum initializes the array with 16 empty addresses. This is why we check for an empty address string rather than null or other falsey value.

  • Once a petId with a corresponding address is found, we disable its adopt button and change the button text to "Success", so the user gets some feedback.

  • Any errors are logged to the console.

解説

  • デプロイされたAdoption契約にアクセスしてから、getAdopters()そのインスタンスを呼び出します。
  • 最初にadoptionInstanceスマートコントラクトコールの外側で変数を宣言するので、最初に取得した後にインスタンスにアクセスできます。
  • call()を使用すると、完全なトランザクションを送信しなくてもブロックチェーンからデータを読み取ることができます。つまり、エーテルを費やす必要がなくなります。
  • 電話getAdopters()をした後、私たちはそれらすべてをループし、各ペットに住所が保存されているかどうかを確認します。配列にはアドレス型が含まれているので、Ethereumは16個の空のアドレスで配列を初期化します。これが、nullや他の誤った値ではなく空のアドレス文字列をチェックする理由です。
  • いったんpetId対応するアドレスを持つが発見され、私たちは、そのボタンを採用し、ボタンのテキストを「成功」に変更無効にするので、ユーザーは、いくつかのフィードバックを取得します。
  • エラーがあればコンソールに記録されます。

Handling the adopt() Function

(1)handleAdoptの中のコメントを削除し、以下のコードを入力します。

var adoptionInstance;

web3.eth.getAccounts(function(error, accounts) {
  if (error) {
    console.log(error);
  }

  var account = accounts[0];

  App.contracts.Adoption.deployed().then(function(instance) {
    adoptionInstance = instance;

    // Execute adopt as a transaction by sending account
    return adoptionInstance.adopt(petId, {from: account});
  }).then(function(result) {
    return App.markAdopted();
  }).catch(function(err) {
    console.log(err.message);
  });
});

Things to notice:

  • We use web3 to get the user's accounts. In the callback after an error check, we then select the first account.

  • From there, we get the deployed contract as we did above and store the instance in adoptionInstance. This time though, we're going to send a transaction instead of a call. Transactions require a "from" address and have an associated cost. This cost, paid in ether, is called gas. The gas cost is the fee for performing computation and/or storing data in a smart contract. We send the transaction by executing the adopt() function with both the pet's ID and an object containing the account address, which we stored earlier in account.

  • The result of sending a transaction is the transaction object. If there are no errors, we proceed to call our markAdopted() function to sync the UI with our newly stored data.

解説

  • ユーザーのアカウントを取得するためにweb3を使用します。エラーチェック後のコールバックで、最初のアカウントを選択します。
  • そこから、上で行ったようにデプロイ済みの契約を取得し、インスタンスをに格納しますadoptionInstance。ただし今回は、通話の代わりにトランザクションを送信します。トランザクションには「差出人」アドレスが必要であり、それに関連したコストがかかります。エーテルで支払われるこの費用はガスと呼ばれます。ガス代は、スマートコントラクトに計算を実行したりデータを保存したりするための料金です。adopt()ペットのIDと、先ほど保存したアカウントアドレスを含むオブジェクトの両方を使用して関数を実行してトランザクションを送信しaccountます。
  • トランザクションを送信した結果がトランザクションオブジェクトです。エラーがなければmarkAdopted()、UIを新しく保存したデータと同期させるために関数を呼び出します。

これで準備完了。 完成コードは以下の通り(2019/7/9時点) 注:ちょっと自信ないです。ここ違う!ってところあったら教えてください

App = {
  web3Provider: null,
  contracts: {},

  init: async function() {
    // Load pets.
    $.getJSON('../pets.json', function(data) {
      var petsRow = $('#petsRow');
      var petTemplate = $('#petTemplate');

      for (i = 0; i < data.length; i ++) {
        petTemplate.find('.panel-title').text(data[i].name);
        petTemplate.find('img').attr('src', data[i].picture);
        petTemplate.find('.pet-breed').text(data[i].breed);
        petTemplate.find('.pet-age').text(data[i].age);
        petTemplate.find('.pet-location').text(data[i].location);
        petTemplate.find('.btn-adopt').attr('data-id', data[i].id);

        petsRow.append(petTemplate.html());
      }
    });

    return await App.initWeb3();
  },

  initWeb3: async function() {
    if (window.ethereum) {
      App.web3Provider = window.ethereum;
      try {
        await window.ethereum.enable();
      } catch (error) {
        console.error("User denied account access")
      }
    }
    else if (window.web3) {
      App.web3Provider = window.web3.currentProvider;
    }
    else {
      App.web3Provider = new Web3.Providers.HttpProvider('http://localhost:7545');
    }
    web3 = new Web3(App.web3Provider);
    return App.initContract();
  },

  initContract: function() {
    $.getJSON('Adoption.json', function(data) {
      var AdoptionArtifact = data;
      App.contracts.Adoption = TruffleContract(AdoptionArtifact);
      App.contracts.Adoption.setProvider(App.web3Provider);
      return App.markAdopted
    })

    return App.bindEvents();
  },

  bindEvents: function() {
    $(document).on('click', '.btn-adopt', App.handleAdopt);
  },

  markAdopted: function(adopters, account) {
    var adoptionInstance;

    App.contracts.Adoption.deployed().then(function(instance) {
      adoptionInstance = instance;

      return adoptionInstance.getAdopters.call();
    }).then(function(adopters) {
      for (i = 0; i < adopters.length; i++) {
        if (adopters[i] !== '0x0000000000000000000000000000000000000000') {
          $('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);
        }
      }
    }).catch(function(err) {
      console.log(err.message);
    });
  },

  handleAdopt: function(event) {
    event.preventDefault();

    var petId = parseInt($(event.target).data('id'));

    var adoptionInstance;

    web3.eth.getAccounts(function(error, accounts) {
      if (error) {
        console.log(error);
      }

      var account = accounts[0];

      App.contracts.Adoption.deployed().then(function(instance) {
        adoptionInstance = instance;

        return adoptionInstance.adopt(petId, {feom: account});
      }).then(function(result) {
        return App.markAdopted();
      }).catch(function(err) {
        console.log(err.message);
      });
    });
  }

};

$(function() {
  $(window).load(function() {
    App.init();
  });
});

MetaMaskのインストールと設定

Ethereumのベターなウォレット。Chromeが一番簡単だと思うけど、FireFoxでも出来る見たい。 ちなみに自分はデフォルトをsafariにしているんだけど、しばらくそのことに気づかなくて、動かない!ってなってました。なので同じようにsafariをデフォルトにしている人は気をつけよう。

(1) MetaMaskをインストール(これは適当にググれば出てくる)

(2) インストール後はブラウザーの指示に従って進む

(3)After clicking Getting Started, you should see the initial MetaMask screen. Click Import Wallet. ここでinportを選ぶことで先ほど作ったGanacheに接続できます。すでにログインした場合は一度ロフアウトすると出来ると思う(多分ログイン状態でも出来るんだろうけど、自分はうまくいかなかった。出来るなら気にしないでください) f:id:mochi-mochi-0397:20190709182337p:plain

(4)Next, you should see a screen requesting anonymous analytics. Choose to decline or agree. f:id:mochi-mochi-0397:20190709173111p:plain

(5) Ganacheのニーモニックを入れましょう。任意のパスワードを入れてimportしたらOK.

Warning: Do not use this mnemonic on the main Ethereum network (mainnet). If you send ETH to any account generated from this mnemonic, you will lose it all!

Enter a password below that and click OK.

f:id:mochi-mochi-0397:20190709173133p:plain

(6) ここまで全て順調にいけば以下の画面が出ます。Doneを押して終了 f:id:mochi-mochi-0397:20190709173117p:plain

(7)Now we need to connect MetaMask to the blockchain created by Ganache. Click the menu that shows "Main Network" and select Custom RPC.

これで、MetaMaskをGanacheによって作成されたブロックチェーンに接続する必要があります。「メインネットワーク」と表示されているメニューをクリックし、「カスタムRPC」を選択します。

f:id:mochi-mochi-0397:20190709173130p:plain

(8) In the box titled "New Network" enter http://127.0.0.1:7545 and click Save.

http://127.0.0.1:7545[ 新しいネットワーク] というタイトルのボックスに「保存」と入力してクリックします。

f:id:mochi-mochi-0397:20190709173121p:plain

The network name at the top will switch to say http://127.0.0.1:7545.

(9) 右上のバツボタンを押して戻ります。 Ganacheによって作成された各アカウントには100ETHが与えられます。しかしコントラクト自体が展開されたときとテストが実行されたときに一部のガスが使用されるため、一番上のアカウントのがわずかに少なくなります。なのでその時のガス代によって変動します。

f:id:mochi-mochi-0397:20190709182642p:plain

これで設定は終わり! (ウィンドウを狭くすると、Ethの残高部分が振込ボタンと重なる。そういうとこやぞ、MetaMask)

Installing and configuring lite-server

サーバとの関係説明。面倒なので翻訳はなし

We can now start a local web server and use the dapp. We're using the lite-server library to serve our static files. This shipped with the pet-shop Truffle Box, but let's take a look at how it works.

(1) Open bs-config.json in a text editor (in the project's root directory) and examine the contents:

{
  "server": {
    "baseDir": ["./src", "./build/contracts"]
  }
}

This tells lite-server which files to include in our base directory. We add the ./src directory for our website files and ./build/contracts directory for the contract artifacts.

We've also added a dev command to the scripts object in the package.json file in the project's root directory. The scripts object allows us to alias console commands to a single npm command. In this case we're just doing a single command, but it's possible to have more complex configurations. Here's what yours should look like:

"scripts": {
  "dev": "lite-server",
  "test": "echo \"Error: no test specified\" && exit 1"
},

This tells npm to run our local install of lite-server when we execute npm run dev from the console.

Using the dapp

(1) local web serverの立ち上げは以下を入力するだけ。簡単だね

npm run dev

実行すると自動でウィンドウが立ち上がる。 もしsafariで立ち上げてしまったらChromeFireFoxで表示させよう。 f:id:mochi-mochi-0397:20190709182906p:plain

(2) A MetaMask pop-up should appear requesting your approval to allow Pete's Pet Shop to connect to your MetaMask wallet. Without explicit approval, you will be unable to interact with the dapp. Click Connect.

MetaMaskポップアップが表示され、Pete's Pet ShopがあなたのMetaMask財布に接続できるようにするためのあなたの承認を要求するはずです。明示的な承認がないと、dappと対話することはできません。接続をクリックします。 自分はweb画面のadoptをクリックしたら出た。

f:id:mochi-mochi-0397:20190709182951p:plain

(3) 適当な犬を選択して押す

(4) トランザクションを承認するかどうかを確認されるので、承認します。 f:id:mochi-mochi-0397:20190709173138p:plain

(5) web画面が"Success"となっていたらOK f:id:mochi-mochi-0397:20190709173046p:plain

Note: If the button doesn't automatically change to say "Success", refreshing the app in the browser should trigger it.

And in MetaMask, you'll see the transaction listed: f:id:mochi-mochi-0397:20190709173141p:plain

ということで、これにてチュートリアル終わり!

まとめ&所感

テストツールまで入っているのがとてもいい。あと思った以上に簡単だし、わかりやすかった。ただこれだけでは到底自作コントラクトは作れないと思うので、さらなる精進が必要かな。

ブロックチェーン関連で卒論を書くという事

はじめに

卒論終わったぜーーーーーーーー!!!!!!!

はい、これが言いたいだけです。気分を悪くされた方はすみません。先日卒論発表も終わり、無事受理される事になりました。もちろんまだまだ卒論描き途中で路頭に迷っている人もいるとは思いますが、そういうのは完全に無視して、開放感に浸りながら幸せオーラ全開で生きています。

さて、本ブログ最初の記事で(本ブログの趣旨とか開設経緯を紹介するよ - 卒論から始まるBlockchain)こんなことを書いてました。

本ブログの趣旨は『Blockchainに興味を持った人が、卒論を書くための前提知識を持てるようにする』になります!(卒論のレベルは適当)

まあこのブログを読んだから卒論が書ける訳ではないですが、今回ブロックチェーン関連(正確にはLightning Network)をテーマに卒論を書きました。そこで実際やってみてどうだったかというのを書きます。これから卒論書こうと思うんだけど、ブロックチェーン関連で書いていいのかな?気をつける事とかある?書いてみてどうだった?みたいな疑問を持っている方の参考になればと思います。もちろん完全な主観ですので、そこは悪しからず。

トピック

  1. 書いた感想
  2. 必ず得なければならないモノ
  3. 肝に命じておく事
  4. 覚悟するべき現実
  5. テーマの決め方
  6. アップデートの恐怖
  7. 相談出来る人を見つけよう
  8. よかった事
  9. おわりに

1. 書いた感想

めんどくさいし大変

すみません。嘘偽りなく述べるとこれしか出ません。ただこの大半は卒論を書く行為が9割を占めています。何かを強制されると途端に面倒でつまらなくなるじゃないですか。それです。もしかしたら「半端ない卒論書いて、業界に一目置かれるようなものを書いてやるぜ!」とかいう方がいたら、現実はそうは行かんぞみたいなのを伝えたかっただけです。書こうとしている時のモチベーションは、自己啓発本読んだ後に湧き出る謎のやる気程度である可能性があるので、理想と現実を乖離させすぎないようしましょう。 という事で次から真面目に話します。

f:id:mochi-mochi-0397:20190205153405p:plain

2. 必ず得なければならないモノ

ブロックチェーン関連で卒論を書く際、必ず得なければならないモノがあります。それは

教授の承諾

です。これなくしては何も進みません。あなたがどれだけ書きたいと願っても、お歳暮を送ってご機嫌をとっても、教授が「わかった、それで書いていいよ」とならなくては何も進みません。なのでまず最初にブロックチェーンを題材に卒論を書いていいか確認しましょう。

ちなみに自分は「ブロックチェーン関連で書こうと思っているんですが良いですか!!??」と言ったら「よくわからないけど、それで書きたいなら書いて良いよ」と怖いぐらいあっさり認めてくれました。

f:id:mochi-mochi-0397:20190205153914p:plain

3. 肝に命じておく事

おそらく教授はブロックチェーンのことは知りません。なのであなたが一から教える必要があります。

これはしっかり肝に命じておきましょう。もちろん中には知っている、もしくは詳しいという方もいらっしゃとは思いますが、日本全国の教授を見たときに、それを専門としている教授はほとんどいないでしょう。つまり、ブロックチェーンを題材に書くということは教授の専門外で書くという事になります。これを許してくれるか、許してくれないかというのは完全に教授次第です。(許してくれるから良い教授という事ではない。あくまでその人の思想の違いだと思います。) また、もし許可が出たとしても"ブロックチェーンとはなんぞや"という事を最初から説明する必要があります。それが出来ないと、永遠に「で、君は何が言いたいの?この研究の目的は何?これで何が解決するの?」というセリフを繰り返されることになります。地獄です。 しかも、頑張って説明しても毎回狐につままれたような顔をされます。あれ?これでも伝わらんの?となります。もちろんそこは自分の実力不足なんですが、やる度に悲しくなります。そういう部分を頑張って超えなければならないという事を肝に命じておきましょう。なのでそういうのが苦手な人はやめた方が良いと思いますので、しっかり自分の胸に手を当てて確かめた上で挑みましょう。

f:id:mochi-mochi-0397:20190205154748p:plain

4. 覚悟するべき現実

無事許可が出たとしたら、自分が決めたテーマに沿って調査、実験、考察などを行いますね。ここで重要なのは「教授の力を借りれない」という事です。理系ならこのプログラム動かないんですがとか、思ったようなデータが取れないんですがとか、文系ならこの考え方はあってますかとかでしょうか。もしかしたら教授に対して殺意に似た感情を持っている方もいるかもしれませんが、教授というのは何かの専門家です。なので普通なら研究で困ったときに適切なアドバイスを得られる可能性がありますが、専門外で書く場合は無理です。実際に自分もソフトが動かなくて困っていたとき、周りは教授に聞いたりして解決していましたが、自分はそれが出来なくて大変でした。さらに文献も揃っていません。ちょっと困ったから参考文献を探して解決!とかありません。自分の研究室はグラフ理論や最適化理論などの研究室であった為、当然ブロックチェーン に関する文献はありません。必要な文献は教授に言えば買ってもらえましたが、他の研究室がどうかは知りません。予算がかつかつな研究室だとさらに大変ないのではないでしょうか。

また研究室内であれば似たような研究をしている人どうしで相談出来ますが、それが出来ない可能性が高いです。自分はたまたま興味を持ってくれている人がいた為、相談できましたがそういう状況にない場合もあるでしょう。 なので「困っても教授や、研究室の書籍はない。頼りになる友達もいない可能性が高い」という現実を覚悟しておいた方が良いです。

f:id:mochi-mochi-0397:20190205154824p:plain

5. テーマの決め方

全ては教授次第です。本末転倒ですが、これが真理だと思います。その上で自分が興味のあるものと絡めてやってみるといいでしょう。気になるプロジェクトを調査して実際にどういう効用が出ているか調べるとか、ソフトを使ってアプリケーションを作りましたとか、分析してみましたとかです。まあ教授の承諾が得られるそれっぽいものにすれば大丈夫だと思います。

6. アップデートの恐怖

この界隈は本当に速いですね。秒進分歩ペースで進んでいるんじゃいかと思うほど速いです。それ自体はとても良いことですが、論文を書くとなると大変です。なぜなら論文に書いていたソフトがアップデートされて、提出する時には動かないということが起こりえるからです。自分は動かないまでは行きませんでしたが、使っていたソフトのGUIが変わって、載せた画像が古くなってしまいました。

また技術仕様も変わると、途中の説明も間違った事になる可能性もあります。書いていて、「あれ、そう言えばなんか新しいプルリクが上がっていた気がするけど、もう実装されたんか?」とかなります。なので、早めに書き終えて提出するだけの状態にするんだ!というコスパの良い人生を送りたい人はやめた方が良いかもしれません。別にこの分野に限った事ではありませんが、書く以上は最低限テーマに関する情報収拾は頻繁に行った方が良いと思います。そういう意味で、苦痛にならない分野、興味を持てるものにした方が良いでしょう。

f:id:mochi-mochi-0397:20190205154137p:plain

7. 相談出来る人を見つけよう

『4. 覚悟するべき現実』で

とにかく「困っても教授や、研究室の書籍はない。頼りになる友達もいない可能性が高い」という現実を覚悟しておいた方が良いです。

と書きましたが、だからこそ相談できる人を見つけておきましょう。思った通りにプログラムが動きませんとか、ここがよくわからないんですがどういう事ですか?などです。自分はたまたま同じ研究室にいた少しブロクチェーンに詳しい友達に助けて貰ったり、インターン先やイベントで知った人に聞いたりしました。ただ「何がわからないかわかっていないんだけど、自分が理解できていないことはわかる」みたいな状況あるじゃないですか。そういう「なんて質問したら良いかわからない」というレベルでも気軽に相談できる人はすごく貴重なので、そういう人を頑張って見つけられると良いと思います。

8. よかった事

ここまでかなり沢山ネガティブな事をいった気がしますが、もちろんよかった事もいっぱいあります。一番は『理解が深まる』でしょう。 何を当然な事をという感じがしますが、侮ってはいけません。この技術の特性上、追うべき情報は多岐に渡ります。気になっているプロジェクトの最新情報やアップデート情報、業界を代表する企業のインタビュー記事や買収の話、思想や将来の業界展望の話、などなど挙げたらきりがありません。そのため、知識としてはどうしても広く浅くなってしまいます。一方論文を書くということは何か専門的な新しい事をするわけです。そうする事で、一つ自分の中で詳しい分野などができるのは一つの自信になりますし、ポジションをとる事もできます。これは普段SNSを眺めて、適当に気になった情報を追いかけるだけでは絶対に無理だろうなという知識が得られます。まあこれを価値あることかと思うかは各個人の自由ですが、個人的にはとても良かったと思っています。

他にもゼロベースで誰かに教える機会が増えるのでブロックチェーンの基礎的部分から復習することができますし、どうすれば理解してもらえるかという事を考える事で、説明に必要なポイントを探すことができます。 そういう意味で研究発表を通して、小さいコミュニティーで、かつ自分しかその知識を持っていないという状況でアウトプットする場があったのはとても幸せでした。

f:id:mochi-mochi-0397:20190205154358p:plain

9. おわりに

ブロクチェーン関連(もしくは暗号通貨関連)で卒論を書きたい人は教授の承諾を得て書こう!でもその場合は教授の力を借りれないと思うから、頑張ってね!!

が結論です。

// そんな事も知らねぇのかよ、って感じで教授に教えるのがオススメです。ちょっとしたストレス発散になります。

Macにローカル環境で動くTestnet版BTCPay serverを入れてみよう

まえおき

 みなさん Bitcoin 払いで買い物をしたことがありますか?自分は...ありません。せっかくなら使ってみたいと思いませんか?そんなあなたに今日ご紹介するのが

「BTCPay server」です!

github.com

これを使えば一連の Bitcoin 払いの流れを構築する事が可能です。ということで今日はローカル環境で Testnet 版 BTCPayserver を入れてみたいと思います。

やり方はこちらを見ながらやっていきます。

github.com

 

環境

PC:Mac

OS:Mojava 10.14.1

 

BTCPay serverとは

Bitcoin, altcoin, Lightning Network などに対応したペイメントプロセッサーになります。ペイメントプロセッサーとは決済代行サービスのことで、Visa や Mastercard などがそれに当たります。詳しくはこちらのブログが非常によくまとめられています。(BTCPayServer はモナコインのサービスをどう変える? (2 of 3)|cryptcoin_junkey|note

 

BTCPayServer は、登録したユーザごとに、店舗を設定し、請求時点でのコインのレートを考慮した請求書を発行し、回収のための QR コードを表示する画面を表示し、回収が成功できたかどうかまでを管理します。

BTCPayServer はモナコインのサービスをどう変える? (2 of 3)|cryptcoin_junkey|note

 また、github ではユースケースも紹介されています。(btcpayserver-doc/UseCase.md at master · btcpayserver/btcpayserver-doc · GitHub

オンラインストア、一般店舗の支払い用としても活用できます。

 

全体像

f:id:mochi-mochi-0397:20181113131453p:plain

btcpayserver-doc/Architecture.md at master · btcpayserver/btcpayserver-doc · GitHub

 自分は最初勘違いしていたのですが、BTCPayserver はあくまで「ペイメントプロセッサー」です。なので BTCPay server だけBitcoin 払いの店ができるわけではありません

大事なのでもう一度言います。

 ”BTCPay server だけBitcoin 払いの店ができるわけではありません。”

つまり Bitcoin Core をインストールし、フルノードを立てる必要があります( Bitcoinを使用する場合は Bitcoin Core 。有料ならそんなことしなくても出来る見たいです。)

実際に必要なものとして以下が書かれています。これらは最低限必要なものですので、サーバー、もしくはPCに入れて連結する必要があります。

The minimal setup involves:

btcpayserver-doc/Architecture.md at master · btcpayserver/btcpayserver-doc · GitHub

 

事前準備① Bitcoin Coreを入れる

先ほど説明したように、Bitcoin Core を使います。まだないよという方は以下の記事を参考に入れてください。また今回は testnet に繋げるので、testnet に繋げた状態で起動してください。

mochi-mochi-blockchain.hatenablog.com 

coin-wave.com

 

NBXplorer

A minimalist UTXO tracker for HD Wallets.

GitHub - dgarage/NBXplorer: NBitcoin Explorer

らしいです。*よくわからなかったので説明を省きます。ごめんなさい。UTXOをトラックするwalletってなに!?

まあとにかく必要みたいなのでインストールしましょう。ただ、その前 .NET core SDK  というものが必要です。以下のリンクに飛び、Dawnload ボタンを押しましょう。ダウンロード後は開封して画面にしたがっていけばOKです。

dotnet.microsoft.com

 

.NET Core SDKはすでにあるよ&インストール終わったよという方は以下のコマンドを打ってインストール、ビルドをしましょう。インストール場所は無難に Users/ユーザー名 とかでいいのではないでしょうか。自分は Users/toshiki に入れました。

  • $ git clone https://github.com/dgarage/NBXplorer
  • $ cd NBXplorer
  • $ ./build.sh

f:id:mochi-mochi-0397:20181203235457p:plain

f:id:mochi-mochi-0397:20181203235538p:plain

 

ビルドに問題なく成功したら以下を入力して起動します。( Bitcoin Core の同期が終わっていないとエラーが出るかもしれません。その時は同期が終わるまで待ちましょう。)

 

$ ./run.sh -testnet --btcrpcuser=bitcoin.confで設定したrpcuser名 --btcrpcpassword=bitcoin.confで設定したrpcpassword名

f:id:mochi-mochi-0397:20181206120423p:plain

 

BTCPayserver

いよいよ本丸です。インストール、ビルドは以下のコマンドです。シンプル!インストール場所は先ほどと同じように Users/ユーザー名 にしておきます。

 

$ git clone https://github.com/btcpayserver/btcpayserver.git

$ cd btcpayserver

$ ./build.sh

 <git clone>

f:id:mochi-mochi-0397:20181203235335p:plain
 

<./build.sh>

f:id:mochi-mochi-0397:20181203235314p:plain

続きを読む

bitcoindが起動しなくなった

まえおき

タイトル通りbitcoindが起動しなくなりました。正確にはターミナルから$bitcoind を打っても起動しなくなりました。

Lightning Network を触ってみようと思ってLNDをインストールしていたんですが、それでなんかやってしまったのではないかと思っています。しばらく悲しみに打ちひしがれて、解決策も全く出なくて死んでたのですが、なんとか治ったので記事にしました。同じようなエラーが出た人の助けになれば幸いです。

 

環境

PC:Mac

OS:Mojava 10.14.1

Bitcoin Core:0.17.0.1

 

状況

普通は $bitcoind で動くはずなのに、以下のエラーが出る。

$ bitcoind

dyld: Symbol not found: __ZN5boost6system15system_categoryEv

  Referenced from: /usr/local/bin/bitcoind

  Expected in: /usr/local/opt/boost/lib/libboost_system.dylib

in /usr/local/bin/bitcoind

Abort trap: 6

でも、Finderから直接起動するとちゃんと動く

f:id:mochi-mochi-0397:20181103005239p:plain

f:id:mochi-mochi-0397:20181103005340p:plain

GUIを起動した状態で bitcoin-cli をやっても同じエラーが出る。

$ bitcoin-cli -getinfo

dyld: Symbol not found: __ZN5boost6system16generic_categoryEv

  Referenced from: /usr/local/bin/bitcoin-cli

  Expected in: /usr/local/opt/boost/lib/libboost_system.dylib

 in /usr/local/bin/bitcoin-cli

Abort trap: 6

 

原因 

リンクがうまくできていないらしい。でもなぜそうなったかはわからない(誰かわかりそうな人いたら教えてください)

 

解決策

以下を入力するだけ。

$ brew link --overwrite bitcoin

 実行後、リンクがうまくされていればターミナルで bitcoind と入力すれば普通に起動した。

 

解決策までの道のり

調査①

とりあえずググるとこんなのが出てきました。

github.com

これだ!絶対これだ!!

どうも boost ってやつがインストール出来ていない、もしくはうまくいってないらしい。

$ brew install boost

このあと起動を心みるが、同じエラーが出る。試しに次もやってみる。

$ brew reinstall boost

これも同じくうまくいかない。一回消してもう一回やれば?みたいなのも書いてあるので試してみる。

$ brew uninstall --force boost

Error: Refusing to uninstall /usr/local/Cellar/boost/1.68.0

because it is required by bitcoin, which is currently installed.

You can override this and force removal with:

  brew uninstall --ignore-dependencies boost

なんか拒否しているみたいなので、最後に出たコマンドを使って強制的にアンインストールしてみる。その後もう一度インストール。

$ brew uninstall --ignore-dependencies boost

$ brew install boost

で、もう一回 $bitcoind で起動してみる。

$ bitcoind

dyld: Symbol not found: __ZN5boost6system15system_categoryEv

  Referenced from: /usr/local/bin/bitcoind

  Expected in: /usr/local/opt/boost/lib/libboost_system.dylib

in /usr/local/bin/bitcoind

Abort trap: 6

はい、予想できてました。ありがとうございます。

f:id:mochi-mochi-0397:20181128175857p:plain


下の方に行くと、同じように解決してない人がいる様子。その場合は

1. 正しいboostのバージョンをインストールしろよ。

2. bitcoindを再ビルドしろ

の二つが提案されている。

でもさっきboostの最新版インストールしちゃったし、これからバージョン下げるとか怖いし、やり方わからんし。。。

make は問題なく通るので、解決せず。少し深めのため息をしながら天を仰ぐ。

 

調査②

きっとPATHがうまく通ってないんだな、と思って探したら以下を発見。

qiita.com

これはboostというc++のライブラリが、bicoindのコンパイル時にうまくリンク出来てない為に発生します。otoolで確認するとやはりリンクがされてないようです。

続きを読む

Macにbitcoindと連携したLNDを入れてみよう(テストネット)

 まえおき

今回はbitcoindと連携した LND を入れてみましょう。また接続ネットワークはテストネットです。

Lighntning Network が何かわからない人は以下の記事を読んでみましょう。

thinkit.co.jp

今回は Lighitning Network を実際に触ってみるために、Lightning Labs 社が開発している LND というソフトをダウンロードし、接続確認までやってみたいと思います。

 

今日の流れ

  1. 事前準備
  2. LND のインストール
  3. 起動
  4. 起動確認
  5. 途中遭遇したエラーたち

以下の流れに沿ってやっていきます。

github.com

また、日本語文献では以下のものを多いに参考にさせていただきました。

medium.com

m0t0k1ch1st0ry.com

 

実行環境

PC:Macbook Pro

OS:Mojave 10.14.1

 

LNDとは

LNDは Lightning Labs 社が開発した Lightning Network 用ソフトになります。かなり活発に動いており、Lightning Network を動かすソフトウェアとしては代表的なものになります。

ちなみに、最初は自分も誤解していたのですが、Lightning Network は統一された仕様があるわけではありません。各社がそれぞれの仕様で研究開発をし、実装しています。有名どころだと Eclair Wallet で有名な ACINQ 社の eclair(GitHub - ACINQ/eclair: A scala implementation of the Lightning Network.)や Blockstream 社が開発しているc-lightning(GitHub - ElementsProject/lightning: c-lightning — a Lightning Network implementation in C)が有名です。

 

事前準備①:Go言語のインストール

さあ、早速ダウンロードしてみるか!といきたいとこですが、そうはいきません。何事も事前準備が必要です。LND は Go 言語で書かれているため、まずはそれをダウンロードしなければなりません。

$ brew install go

Updating Homebrew...

==> Auto-updated Homebrew!

Updated 1 tap (homebrew/core).

==> Updated Formulae

sphinx-doc     bison            faas-cli         groovy           mariadb@10.0     nano             sceptre          xonsh

ammonite-repl    bitwarden-cli    fatsort          groovysdk        mariadb@10.1     netdata          scrcpy

autopep8         botan            folly            jetty            meson            node-build       shfmt

bat              docker-machine   gflags           krakend          minizinc         pgcli            tbb

beagle           emscripten       gitlab-gem       libpq            mkvtoolnix       prettier         tile38

beast            eslint           gjs              libtensorflow    mongodb          sbcl             v8

 

==> Downloading https://homebrew.bintray.com/bottles/go-1.11.2.mojave.bottle.tar.gz

######################################################################## 100.0%

==> Pouring go-1.11.2.mojave.bottle.tar.gz

==> Caveats

A valid GOPATH is required to use the `go get` command.

If $GOPATH is not specified, $HOME/go will be used by default:

  https://golang.org/doc/code.html#GOPATH

 

You may wish to add the GOROOT-based install location to your PATH:

  export PATH=$PATH:/usr/local/opt/go/libexec/bin

==> Summary

🍺  /usr/local/Cellar/go/1.11.2: 9,282 files, 404MB

パスを通す必要があるので以下を入力します。

$ export GOPATH=~/gocode

$ export PATH=$PATH:$GOPATH/bin

また、起動する度にこれを入力すのは面倒なので .bashrc にこれを書き込んでおきます。

$ vim ~/.bashrc

を入力後 i を押して入力可能状態にし、以下を入力します。

export GOPATH=~/gocode

export PATH=$PATH:$GOPATH/bin

入力後excを押して :wq を入力して enter を押します。

終わったらパスを反映させるために以下を入力

$ source ~/.bashrc

 これで go の設定は終了です。

 

事前準備②:bitcoindのインストール

この前入れたばかりなのでこの記事を参考にどうぞ

Macなら $brew install bitcoin で入れるのが一番簡単かと思われます。(記事には書いていません)

mochi-mochi-blockchain.hatenablog.com

 

また、今回は LND を起動するときに bitcoind が動いていないといけないのでインストールが終わったら起動させておきましょう。(起動させていないと永遠にエラーが出続けます)

 

LNDのインストール

 ここはとってもシンプル。以下のコマンドを入力するだけ。

$ go get -d github.com/lightningnetwork/lnd

$ cd $GOPATH/src/github.com/lightningnetwork/lnd

$ make && make install

今回ダウンロードしたLNDのバージョンを見てみましょう。

$ lnd --version

lnd version 0.5.0-beta commit=v0.5-beta-325-g721e9a2a90eb6a3eb985b02af6c56762026aa16f

ちゃんとインストールされているか確認してみます。

$ which lnd

/Users/toshiki/gocode/bin/lnd

よし!ちゃんと入ってる。

 

事前準備③ bitcoin.confを書き換える

bitcoind にデフォルトで繋がるように bitcoin.conf を書き換えましょう。

場所はここにあるはずです。Library/Application\ Support/Bitcoin/bitcoin.conf

$ vim Library/Application\ Support/Bitcoin/bitcoin.conf

rpcuser=自分で設定したrpcuser名

rpcpassword=自分で設定したrpcpassword名

 

testnet = 1

txindex = 1

server = 1

rest = 1

daemon = 1

 

rpcport = 18332

zmqpubrawblock=tcp://127.0.0.1:28332

zmqpubrawtx=tcp://127.0.0.1:28333

 

事前準備③ lnd.confを作る

上と同じように bitcoind をデフォルトにするために lnd.conf を作ります。

$ mkdir ~/.lnd && cd ~/.lnd

$ vim lnd.conf

中身はこんな感じ

#[Application Options]

debuglevel=info

alias=tosiki //任意の名前

lip=180.X.XXX.XXX:XXX //自分が使っているIPアドレス

 

#[bitcoin]

bitcoin.active=1

bitcoin.testnet=3

bitcoin.node=bitcoind

 

#[Bitcoind]

bitcoind.rpchost=localhost

bitcoind.rpcuser=bitcoin.confで設定したrpcuser名

bitcoind.rpcpass=bitcoin.confで設定したrpcpass名

bitcoind.zmqpath=tcp://127.0.0.1:28332

alias はなんでも大丈夫です。(無難に自分の名前でいいかと)lipはIPアドレスを検索して入れましょう。bitcoind.rpcuser、bitcoind.rpcpassは bitcoin.confで設定した名前を入れればOKです。

 

起動

やり方はいくつかあると思うんですが、今回はこんな感じです。

$ lnd --bitcoin.active --bitcoin.testnet --debuglevel=debug --bitcoin.node=bitcoind --bitcoind.rpcuser=bitcoin.confで設定したrpcuser名 --bitcoind.rpcuser=bitcoin.confで設定したrpcpass名 --bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332 --bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333 --no-macaroons

bitcoind.rpcuser=xxxxx、bitcoind.rpcuser=xxxxx の部分はbitcoin.confで設定したものを入れましょう。また最後にある  --no-macaroons はパスを設定しない限り必要になる(たぶん)ので必ず入れましょう。

途中以下の文が出て止まります。

2018-11-29 11:47:14.952 [INF] LTND: Version: 0.5.0-beta commit=v0.5.1-beta-rc1, build=production, logging=default

2018-11-29 11:47:14.952 [INF] LTND: Active chain: Bitcoin (network=testnet)

2018-11-29 11:47:14.952 [INF] CHDB: Checking for schema update: latest_version=6, db_version=6

2018-11-29 11:47:14.960 [INF] RPCS: password RPC server listening on 127.0.0.1:10009

2018-11-29 11:47:14.960 [INF] RPCS: password gRPC proxy started at 127.0.0.1:8080

2018-11-29 11:47:14.960 [INF] LTND: Waiting for wallet encryption password. Use `lncli create` to create a wallet, `lncli unlock` to unlock an existing wallet, or `lncli changepassword` to change the password of an existing wallet and unlock it.

これはウォレットを作るかアンロックしてね、ということですので新しくたぶを開いて操作します。

ウォレットを作るには lncli create で作れるので、早速作ってみます。

$ lncli create

Input wallet password: 

Confirm wallet password: 

 

Do you have an existing cipher seed mnemonic you want to use? (Enter y/n): n

 

Your cipher seed can optionally be encrypted.

Input your passphrase if you wish to encrypt it (or press enter to proceed without a cipher seed passphrase): 

 

Generating fresh cipher seed...

 

!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO RESTORE THE WALLET!!!

 

---------------BEGIN LND CIPHER SEED---------------

 1. abstract   2. nerve      3. control   4. you   

 5. alien      6. resource   7. true      8. travel

 9. crazy     10. erode     11. rubber   12. tank  

13. loan      14. pizza     15. smart    16. heavy 

17. twelve    18. scatter   19. century  20. this  

21. animal    22. board     23. fine     24. tube  

---------------END LND CIPHER SEED-----------------

 

!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO RESTORE THE WALLET!!!

 

lnd successfully initialized!

下に何もエラーが出てなければ成功です。passwordは8文字以上が必要です。passwordを打っている時は何も表示されないので、何も出ないからと言ってびっくりしないようにしましょう。

作ったらアンロックして使えるようにする必要があります。アンロックはlncli unlockでできます。

*もしエラーが出た方は下に書いてある「途中遭遇したエラーたち」を見るといいかもしれません

$ lncli unlock

Input wallet password: 

 

lnd successfully unlocked!

こちらもエラーが出なければ成功です!では LND を起動した時のタブに戻って確認しましょう。

*もしエラーが出た方は下に書いてある「途中遭遇したエラーたち」を見るといいかもしれません

2018-11-29 11:47:14.960 [INF] LTND: Waiting for wallet encryption password. Use `lncli create` to create a wallet, `lncli unlock` to unlock an existing wallet, or `lncli changepassword` to change the password of an existing wallet and unlock it.

2018-11-29 11:47:27.338 [INF] LNWL: Opened wallet

2018-11-29 11:47:27.338 [INF] LTND: Primary chain is set to: bitcoin

2018-11-29 11:47:27.341 [INF] LTND: Initializing bitcoind backed fee estimator

2018-11-29 11:47:27.341 [INF] LNWL: Started listening for bitcoind transaction notifications via ZMQ on tcp://127.0.0.1:28333

2018-11-29 11:47:27.341 [INF] LNWL: Started listening for bitcoind block notifications via ZMQ on tcp://127.0.0.1:28332

2018-11-29 11:47:27.342 [DBG] LNWL: Using minimum fee rate of 253 sat/kw

2018-11-29 11:47:28.016 [INF] LNWL: The wallet has been unlocked without a time limit

2018-11-29 11:47:28.019 [INF] LTND: LightningWallet opened

2018-11-29 11:47:28.022 [INF] LNWL: Catching up block hashes to height 1445818, this will take a while...

ちゃんと動きました!

 

とは言っても本当に動いているか心配なので、もう一つ確認してみます。

先ほどウォレットを作った時のたぶに戻って lncli --no-macaroons getinfo を打って確認しましょう。

$ lncli --no-macaroons getinfo

{

    "identity_pubkey": "0241bb13f9897f1793b20f18f34220228c69346b0244f61962e057b3ea7e331bbb",

    "alias": "0241bb13f9897f1793b2",

    "num_pending_channels": 0,

    "num_active_channels": 0,

    "num_peers": 0,

    "block_height": 1445820,

    "block_hash": "000000000000005aaa4a5bca5d6c65b90a84045637c20817858dfa5ab23aaf27",

    "synced_to_chain": false,

    "testnet": true,

    "chains": [

        "bitcoin"

    ],

    "uris": [

    ],

    "best_header_timestamp": "0",

    "version": "0.5.0-beta commit=v0.5.1-beta-rc1",

    "num_inactive_channels": 0

}

ということで無事インストールから起動まで出来ました!!!

 

番外事前準備:btcdのインストール

LNDにデフォルトで入っている Bitcoin wallet です。今回は bitcoind を使うので必要ないですが、一応載せておきます。

$ make btcd

 make が無事終了したら以下のコマンドで起動。すると同期が始まります。大体2時間ぐらいかかりました。

$ btcd --testnet --rpcuser=REPLACEME --rpcpass=REPLACEME

途中の進捗は以下で観れます。

$ btcctl --testnet --rpcuser=REPLACEME --rpcpass=REPLACEME getinfo

{

  "version": 120000,

  "protocolversion": 70002,

  "blocks": 1444416,

  "timeoffset": 0,

  "connections": 8,

  "proxy": "",

  "difficulty": 12368745.01434309,

  "testnet": true,

  "relayfee": 0.00001,

  "errors": ""

}

 

途中遭遇したエラーたち

①make checkで現れる謎のエラー(未解決)

make check をすると以下のエラーが出ました。調べたけど解決策が現れず、無視しました(・ω・`)

--- PASS: TestNewInvoice (0.00s)

--- PASS: TestDecodeEncode (0.02s)

PASS

ok  github.com/lightningnetwork/lnd/zpay32 (cached)

make: *** [unit] Error 1

 

②lncli createでウォレット作れない問題

$ lncli create

Input wallet password: 

Confirm wallet password: 

 

Do you have an existing cipher seed mnemonic you want to use? (Enter y/n): n

 

Your cipher seed can optionally be encrypted.

Input your passphrase if you wish to encrypt it (or press enter to proceed without a cipher seed passphrase): 

 

Generating fresh cipher seed...

 

[lncli] unable to generate seed: rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: Error while dialing dial tcp 127.0.0.1:10009: connect: connection refused"

接続エラー?なんでだよ!とか思ったけど普通に起動していないだけだった。起動してからウォレットを作りましょう。

 

②lncli unlock でウォレットをunlockできない問題 

$ lncli unlock

Input wallet password: 

[lncli] rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: Error while dialing dial tcp 127.0.0.1:10009: connect: connection refused"

上の ①lncli createでウォレット作れない問題、と同じく起動してないだけだった。LNDを起動してからやりましょう。

 

③ lncli createで謎のエラー & unlock 出来ない問題

$ lncli create

Input wallet password: 

Confirm wallet password: 

 

Do you have an existing cipher seed mnemonic you want to use? (Enter y/n): n

 

Your cipher seed can optionally be encrypted.

Input your passphrase if you wish to encrypt it (or press enter to proceed without a cipher seed passphrase): 

 

Generating fresh cipher seed...

 

!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO RESTORE THE WALLET!!!

 

---------------BEGIN LND CIPHER SEED---------------

ここに24文字の単語がでます。ウォレットを復元する時に必要になるので、大事にメモしておきましょう。

 

---------------END LND CIPHER SEED-----------------

 

!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO RESTORE THE WALLET!!!

[lncli] rpc error: code = Unknown desc = password must have at least 8 characters

わーい、これでウォレットできたぞ!と思ってアンロックしてみると

$ lncli unlock

Input wallet password: 

[lncli] rpc error: code = Unknown desc = wallet not found

そんなウォレットないけど?だって。いやいや、今作りましたけどぉ!?って思っていたら lncli create の一番最後にエラー出てましたね。

[lncli] rpc error: code = Unknown desc = password must have at least 8 characters

 パスワードは8文字以上にしてね、だそうです。( SEED 出来てるから成功したかと思うじゃん。もうちょい見やすくせんかい)

ちゃんとできると先ほどのエラーは出ず、普通にunlock出来ます。

 

④ウォレット作ってアンロックするまで同期進まない問題

$ lnd

2018-11-28 22:51:17.192 [INF] LTND: Version: 0.5.0-beta commit=v0.5.1-beta-rc1, build=production, logging=default

2018-11-28 22:51:17.192 [INF] LTND: Active chain: Bitcoin (network=testnet)

2018-11-28 22:51:17.192 [INF] CHDB: Checking for schema update: latest_version=6, db_version=6

2018-11-28 22:51:17.200 [INF] RPCS: password RPC server listening on 127.0.0.1:10009

2018-11-28 22:51:17.200 [INF] RPCS: password gRPC proxy started at 127.0.0.1:8080

2018-11-28 22:51:17.200 [INF] LTND: Waiting for wallet encryption password. Use `lncli create` to create a wallet, `lncli unlock` to unlock an existing wallet, or `lncli changepassword` to change the password of an existing wallet and unlock it.

ウォレット作ってない、とのこと。作るまで永遠に動きません。別にエラーでもなんでもないですが、ここまでの①、②、③のエラー全て同時に起こっていたため解決までとても時間を要しました。もし同じように悩んでいるなら上のエラーを片付けられているか確認しましょう。

 

⑤getinfo出来ない問題

$ lncli getinfo

[lncli] unable to read macaroon path (check the network setting!): open /Users/toshiki/Library/Application Support/Lnd/data/chain/bitcoin/mainnet/admin.macaroon: no such file or directory

macaroon path おかしいぜ、と言われます。まず macaroon ってなんだよって話ですが、とりあえずこの admin.macaroon の手前まで行ってみてみましょう。

$ cd Library/Application\ Support/Lnd/data/chain/bitcoin

$ ls

testnet

うん。ないね。なんかディレクトリー作らなくてはいけないのか?とか思ったけど、

(check the network setting!)

 って書いてあるし、設定がおかしいのかなと考えて lnd.conf と bitcoin.conf らへんに的を絞って解決策を探る。

 

macaroons ってやつが悪さしとるのでは?と思いっていたら以下の文を発見

To disable macaroons for testing, pass the --no-macaroons flag into both lnd and lncli.

lnd/INSTALL.md at master · lightningnetwork/lnd · GitHub

テスト用で macaroons いらない場合は lnd と lncli の両方に --no-macaroonsつけろよ、ですって。ふむやってみまましょう。

$ lnd --bitcoin.active --bitcoin.testnet --debuglevel=debug --bitcoin.node=bitcoind --bitcoind.rpcuser=bitcoinrpc --bitcoind.rpcpass=5fa4e39eefe0614db0b03e4dcf0fee83 --bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332 --bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333 --no-macaroons

起動したらもう一つのタブで--no-macaroonsを付けてみる。

$ lncli --no-macaroons getinfo

{

    "identity_pubkey": "0241bb13f9897f1793b20f18f34220228c69346b0244f61962e057b3ea7e331bbb",

    "alias": "0241bb13f9897f1793b2",

    "num_pending_channels": 0,

    "num_active_channels": 0,

    "num_peers": 0,

    "block_height": 1445820,

    "block_hash": "000000000000005aaa4a5bca5d6c65b90a84045637c20817858dfa5ab23aaf27",

    "synced_to_chain": false,

    "testnet": true,

    "chains": [

        "bitcoin"

    ],

    "uris": [

    ],

    "best_header_timestamp": "0",

    "version": "0.5.0-beta commit=v0.5.1-beta-rc1",

    "num_inactive_channels": 0

}

おお!出来た!でもめんどくさすぎません?

 

⑥lnd 単体で起動するとよくわからないところで動かなくなる問題(未解決)

20-008noMacbookPro:~ toshiki$ lnd

2018-11-29 11:16:47.951 [INF] LTND: Version: 0.5.0-beta commit=v0.5.1-beta-rc1, build=production, logging=default

2018-11-29 11:16:47.951 [INF] LTND: Active chain: Bitcoin (network=testnet)

2018-11-29 11:16:47.952 [INF] CHDB: Checking for schema update: latest_version=6, db_version=6

2018-11-29 11:16:47.964 [INF] RPCS: password RPC server listening on 127.0.0.1:10009

2018-11-29 11:16:47.965 [INF] RPCS: password gRPC proxy started at 127.0.0.1:8080

2018-11-29 11:16:47.965 [INF] LTND: Waiting for wallet encryption password. Use `lncli create` to create a wallet, `lncli unlock` to unlock an existing wallet, or `lncli changepassword` to change the password of an existing wallet and unlock it.

2018-11-29 11:18:01.686 [INF] LNWL: Opened wallet

2018-11-29 11:18:01.741 [INF] LTND: Primary chain is set to: bitcoin

2018-11-29 11:18:01.742 [INF] LTND: Initializing btcd backed fee estimator

 上の場所から永遠に動きません。ここで getinfo しても⑤と同じエラーが出ます。なぜ?

 

 

まとめ

bitcoind よりはインストールとかは簡単だった印象です。とは言っても四苦八苦する場面が多かったです。

実際に Lightning Network が使われるようになったら面白いけど、一般に普及していくためにはこの導入部分の煩わしさは排除しないといかんなぁと思いました。

後 macaroon って何者???

 

// SOTURON MENDOKUSAI

 

参考資料

lnd/INSTALL.md at master · lightningnetwork/lnd · GitHub

ライトニングノード(Lightning Network/Lnd)を立ち上げてみた! – Tomoya Ishida – Medium

LightningNetworkノードを立ち上げる方法(lnd編) - Qiita

Lightning Networkが動作する仕組み | Think IT(シンクイット)

Bitcoinのテストネットで遊んでみよう

前置き

前回こちらの記事を書いたわけですが(新品Macにbitcoindを入れて、testnetに繋いでみよう - 卒論から始まるBlockchain)、今回はその続きです。ターミナルで簡単なコマンドを叩いたり、アドレスを作って送金してみます!

 

環境

PC:macOS Mojave 10.14.1

Bitcoin Core:v0.17.99.0-51e5ef397

 

接続確認

まずはBitcoin Coreを立ち上げます。前回はFinderから直接立ち上げましたが、今回はかっこよくターミナルから立ち上げてみます。

ターミナルに

$ bitcoin-qt

を打ち込みます。すると前回と同じ画面が自動で立ち上がります。

f:id:mochi-mochi-0397:20181103005340p:plain

GUIはいりません。という方は以下のコマンドで。

$ bitcoin-qt -daemon

立ち上がるとtestnetのブロックを猛烈なスピードでダウンロードし始めます。だいたい30分以上かかった気がします。

2019/04/24時点で約24GBほどです。$ bitcoin-cli getblockchaininfo で出てくるsize_on_diskの値で、キロバイト表記です。)

f:id:mochi-mochi-0397:20190424103551p:plain

ちゃんと立ち上がっているか確認してみます。

$ bitcoin-cli getconnectioncount

1

これは接続されているノードの数です。最初は1から始まり、安定すると8まで行きます。(多分何か手を加えない限り8以上には増えない)

また、以下のコマンドで接続相手の情報を得ることができます。

$ bitcoin-cli getpeerinfo

[

  {

    "id": 0,

    "addr": "58.64.158.246:18333",

    "addrlocal": "180.1.181.232:56614",

    "addrbind": "192.168.11.14:56614",

    "services": "000000000000040d",

    "relaytxes": true,

    "lastsend": 1541487157,

    "lastrecv": 1541487092,

    "bytessent": 12836,

    "bytesrecv": 438096,

    "conntime": 1541486491,

    "timeoffset": 0,

    "pingtime": 0.173859,

    "minping": 0.173092,

    "version": 70015,

    "subver": "/Satoshi:0.16.1/",

    "inbound": false,

    "addnode": false,

    "startingheight": 1414448,

    "banscore": 0,

    "synced_headers": 1414433,

    "synced_blocks": -1,

    "inflight": [

    ],

    "whitelisted": false,

    "minfeefilter": 0.00001000,

    "bytessent_per_msg": {

      "addr": 870,

      "feefilter": 32,

      "getaddr": 24,

      "getheaders": 3255,

      "headers": 1010,

      "inv": 2678,

      "ping": 192,

      "pong": 192,

      "sendcmpct": 66,

      "sendheaders": 24,

      "tx": 4342,

      "verack": 24,

      "version": 127

    },

    "bytesrecv_per_msg": {

      "addr": 30842,

      "feefilter": 32,

      "getdata": 851,

      "getheaders": 1085,

      "headers": 403542,

      "ping": 192,

      "pong": 192,

      "reject": 1120,

      "sendcmpct": 66,

      "sendheaders": 24,

      "verack": 24,

      "version": 126

    }

  } #以下長いので省略

以下のコマンドではブロックの簡単な情報を見ることができます。

※ 最近のバージョンだとgetinfoだけだど、そんなコマンド無いよとエラーが帰ってきました。各バージョンで確認してみましょう。(https://bitcoin.org/en/developer-reference#address-conversion

$ bitcoin-cli -getinfo

{

  "version": 179900,

  "protocolversion": 70015,

  "walletversion": 169900,

  "balance": 0.17838729,

  "blocks": 1442251,

  "timeoffset": 0,

  "connections": 8,

  "proxy": "",

  "difficulty": 11974980.94624031,

  "testnet": true,

  "keypoololdest": 1541155356,

  "keypoolsize": 1000,

  "paytxfee": 0.00000000,

  "relayfee": 0.00001000,

  "warnings": ""

}

 

アドレスを作ろう

テストネットコインをゲットするために、まずはアドレスを作りましょう。以下のコマンドを入力します。

$ bitcoin-cli getnewaddress

2NEfQRrL8JbsYPiGcxEcHJsdbs99PFov1nu

ちなみにアドレスには好きなタグ付けをつけることができます。またタグは後でつけることも可能ですし、変更することもできます。

$ bitcoin-cli getnewaddress mochi

2N1PpDxoLVsZ2EupmwiL7G5KETjpzM6oc19

ちなみにちなみに、Bitcoin Core0.16からsegwitアドレス。つまりP2SHのアドレスがデフォルトになったらしいです。なので、P2PKHのアドレスが欲しいです!という方はlegacyを追加するとできます。(参照:bitcoin core rpc/ getnewaddressでp2pkhアドレスを得るには - Qiita

※P2PSHとP2PKHがよくわかりません!という方はこちらを参照するといいかと:ビットコインおけるトランザクションスクリプトの仕組みとその種類

$ bitcoin-cli getnewaddress "" legacy

mq4fMveBxM5rdRZsarVnFmDs5MvgKvX4hp

 

テストネットコインをゲットしよう

いよいよテストネットコインをゲットしましょう。

色々入手方法はありますが、今回はBitcoin testnet3 faucetを利用します。もちろんテストネットコインなので無料でもらえます。

先ほど作ったアドレスを入力し、「I'm not a robot」にチェックを入れましょう。

f:id:mochi-mochi-0397:20181106201329p:plain

すると以下の画面が表示されます。今回は大体0.08345864BTCが送られました。ちなみに下の方に「使わないなら以下のアドレスに返してね」と書いてありますね。どうもテスト用とはいえ無限ではないようです。

f:id:mochi-mochi-0397:20181106201619p:plain

ちゃんと送られているか確認してみます。

f:id:mochi-mochi-0397:20181106202458p:plain

ちゃんと送られていますね。ただすぐに使えるというわけではなく、まずは検証待ち状態になります。しばらくすると利用可能に取り込まれて使えるようになります。

同期が終わるまでは受け取ることはできません。もし同期前に先ほどの画面で送信処理をした場合は同期後に反映される形になります。自分はそれを知らず「なんで送れないんだよ!!」とイライラしてました。)

ターミナルでも確認してみます。

$ bitcoin-cli listtransactions

[

{

    "address": "2N1PpDxoLVsZ2EupmwiL7G5KETjpzM6oc19",

    "category": "receive",

    "amount": 0.08345864,

    "label": "mochi",

    "vout": 1,

    "confirmations": 2,

    "blockhash": "0000000000096be5be01b856ff0d4768dc2ecafd37b7bfa80aa4ab674c1c9849",

    "blockindex": 25,

    "blocktime": 1541503652,

    "txid": "71ea648cd5754293174df7deec085e8fbf8accffd71da618e88da68e48aaec06",

    "walletconflicts": [

    ],

    "time": 1541502885,

    "timereceived": 1541502885,

    "bip125-replaceable": "no"

  }

]

 

送金してみよう

では先ほどのアドレスにいくらか返してあげましょう。

コマンドは「bitcoin-cli sendtoaddress 送信先アドレス 送信量」です。

$ bitcoin-cli sendtoaddress mv4rnyY3Su5gjcDNzbMLKBQkBicCtHUtFB 0.001

f6794e5c161d6ab423826a9514bfb4af02c83fdc6b7ced687d9d87e1efc723df

するとトランザクションハッシュ値が出てきます。どうなっているかBitcoin Testnet Block Explorer | BlockCypherで確認してみます。上部にある検索欄にトランザクションハッシュ値を入力します。

f:id:mochi-mochi-0397:20181106221032p:plain

するとこんな感じで送金したアカウントの所持金、手数料、何分前のものか、承認数などが出てきます。

f:id:mochi-mochi-0397:20181106221332p:plain
まとめ

アップデートの度にコマンドが変更するのか、自分のバージョンでは"Method not faund"と表示されることが多々ありました。超めんどくさいので何とかしていただきたいo(`ω´ )o

コマンド一覧はこちらから見れます→https://bitcoin.org/en/developer-reference#address-conversion

 

間違い、疑問点、不明点などあればお気軽にコメントくださいm(_ _)m

 

参考資料

アカウントとアドレス - ウォレット - Bitcoin ClockUpMemo

MacにBitcoindをインストールしてテスト送金をしてみる - Qiita

ビットコインおけるトランザクションスクリプトの仕組みとその種類

https://bitcoin.org/en/developer-reference#getinfo

bitcoin core rpc/ getnewaddressでp2pkhアドレスを得るには - Qiita

bitcoin-cliを使ったBitcoinAPI入門 - Qiita

https://bitcoin.org/en/developer-reference#address-conversion

Bitcoin(Mainnet, Testnet)への接続方法 - Qiita

ブロックチェーン・プログラミング 仮想通貨入門 (KS情報科学専門書)

ビットコインとブロックチェーン:暗号通貨を支える技術

 

 

//改めて検索すると結構同じ内容のブログがありますね。バージョンアップ早いので、ここら辺いっぱいあるの助かる。