こんにちは、エンジニアインターンの内村です。

みなさま、NEOの開発はされていますでしょうか? 実は先日開催された、NEOブロックチェーンチャレンジ東京というハッカソンに弊社エンジニアチームで出場いたしまして、有難いことに準優勝を頂くことができました。

この記事では、NEOブロックチェーンチャレンジ東京のイベントの様子や、弊社チームが開発したプロダクトの内容をレポートしたいと思います。

NEOについて

NEO[1]は”中国のEthereum”とも呼ばれる、中国で発足したオープンソースのブロックチェーンで、デジタルアセット + デジタルアイデンティティ + スマートコントラクト = スマートエコノミーを理念に2014年から開発が進められているプロジェクトです。理念に登場するように、NEOブロックチェーン上にスマートコントラクトを実装することが可能になっており、”中国のEthereum”の異名にも頷けます。しかしながら、根幹にあたるプロトコルの仕様は実際Ethereumのそれとは大きく異なっています。以下の表をご覧ください。

Ethereum NEO
コンセンサスアルゴリズム PoS dBFT
基軸通貨 Ether NEO/Neo Gas
ブロック生成者への報酬 Ether NEO
コントラクト実行時のfee Ether Neo Gas
主要スマートコントラクト開発言語  solidity/viper C#/Java/Python

※NEOのコンセンサスアルゴリズムであるdBFTについてはこちら[2]の記事で詳しく説明されています。

ブロック生成のコンセンサスアルゴリズムに始まり、基軸通貨の役割や主となる開発言語もそれぞれ異なっており、根本的な設計からして全くの別物となっています。中でも開発の手触り感として一番違いを感じるのは、スマートコントラクト開発への学習コストの差で、NEOのスマートコントラクトは既存のメジャーな言語による開発が可能なことから、Ethereumに比べ開発への敷居がとても低く、親しみやすくなっています。こういったデベロッパーフレンドリーな開発環境を目指しているのもNEOの文化といえます。

ちなみに、公式ドキュメント[3](2018/9/7現在)によると、現在以下の言語がコントラクトに対応しており、

The languages that are currently supported are: C#, VB.Net, F# Java, Kotlin Python

golangのコンパイラも近々リリースされるようなので、今後益々対応言語が増えると思われます:smile: solidity等の言語を学習するのに億劫になっていた方もぜひNEOのスマートコントラクトに挑戦してみるのはいかがでしょうか。

golangのコンパイラに関してはこちら[4]をご参照ください。

NEOブロックチェーンチャレンジについて

NEOブロックチェーンチャレンジとは、NEO Smart Economy様の主催するハッカソンで、今回弊社チームが参加したハッカソンは8/24(金)から8/26(日)に東京で開催されました。また、中国に母体を持つNEOの本部から審査員がいらっしゃった関係もあり、イベントは基本的に英語で進行されました。

こちらのハッカソンでは4つのテーマが用意されていて、以下の中から選び開発するというスタイルでした。 イベントの詳細に関してはこちら[5]をご参照ください。

  • 初級テーマ: 指定ブロックの高さにおける、指定アドレスの NEO 残高を照会する。
  • 中級テーマA: 指定ブロックの高さにある全アドレスの NEO 残高を取得する。
  • 中級テーマB: オフライン同期パッケージを使用して、 Transaction Output を独自のデータベースシステムに導入する。
  • 上級テーマ: NeoGASを利用するシナリオを探し、システムを開発する。

弊社チームは上級テーマにエントリーし、NeoGASを利用したアプリケーションを開発しました。

ハッカソンの雰囲気

参加者集合写真

NEOの本部から来られた方や海外から参加された方が多く、外国人の比率が3分の1くらいで、グローバルなムードが漂っていました。また、NEOの開発者コミュニティーであるCity Of Zion[6]からO3[7]の方々が見学しに来ていたり、NEOのコミュニティーの大きさを感じました。

弊社チーム構成

今回のハッカソンは、弊社CTOの森下さんと私含む大学生エンジニアインターン2人の計3人で”Colony”というチームを組み出場しました。ハッカソン中の各々の担当領域はざっくりと以下にまとめましたが、特に森下さんには総合的に開発をしていただきました😅

  • 森下 真敬(CTO): サーバーサイド、フロントエンド
  • 南条 宏貴(エンジニアインターン): NEOスマートコントラクト
  • 内村 清太(エンジニアインターン): サーバーサイド、英語資料作成

弊社チームのプロダクト”Sancho”について

肝心の成果物についてですが、弊社チームは”Sancho”という分散型漫画プラットフォームを開発しました。

Sanchoのスクリーンショット

ソースコードはSanchoのgithubリポジトリ[8]にて公開しています。

参考までに、以下がhackathonで使用したslideになります。

開発背景

課題

現在、世界中の漫画業界が海賊版による著作権侵害の被害を被っており、英字新聞社ジャパンタイムズ[9]によると、2014年の時点で漫画市場の海賊版による被害額は日本で約500億円、米国では約1.3兆円に上っています。

“Damage done by pirated comics to the domestic manga market may have reached ¥50 billion, according to research commissioned by the Ministry of Economy, Trade and Industry and released in 2014. The damage in the U.S. was even more severe: An estimated ¥1.3 trillion may have been lost to piracy.”

もちろん、こういった現状を打開すべく、海賊版サイトへの様々な規制や取り締まりが行われてはいますが、著作権を侵害する違法なサイトはあとを絶たず、いたちごっこが繰り返されています。

解決策

上記の問題を考えるにあたって、弊社チームはまず性悪説的な観点から、違法アップロードがなくなることはないという前提に立ち、違法に複製・配布された漫画が読まれることによる作者および出版社への利益がない現状の課題を解決しようと考えました。

そうして結果的に私たちが目指したのは、違法アップロードを許容した上で、さらに漫画をアップロードするインセンティブを設け、アップロードされた作品を通じて作者や出版社に収益が入る、参加者全員に利益をもたらすプラットフォームです。

具体的には、以下の点を踏まえて開発を行いました。

  • 誰でも漫画をアップロードできる。
  • 誰でもアップロードされた漫画を無料で読むことができる。
  • 漫画の作者、出版社に収益が入る。
  • プラットフォームで得られた収益が適切かつ公平に分配される。
  • プラットフォームの管理に透明性がある。
  • 管理者がいない。

こうして弊社チームが開発したのが、NEOのスマートコントラクトを利用した分散型漫画プラットフォームである”Sancho”です。

全体像

Sanchoの全体像

※こちらの全体像はチャーリーさんのツールキット[10]を使用し、作成しました。

サービスのおおまかな流れは、以下のようになっています。

  1. ユーザーが匿名で漫画のimageをSanchoのプラットフォームにアップロードする。
    • 誰でも無料で読むことができるようになる。
  2. アップロードされた漫画の作者は、ユーザーによる投票で選出される。
    • 投票により当選した作者に対し投票をしていたユーザーは少量の収入を得ることができる。
  3. 広告により得られた収益はコントラクトによって自動的に漫画をアップロードした人、作者、投票者に分配される。

使用技術

Sanchoの開発には主に以下のライブラリ及びサービスを利用しました。 詳細は後述しますが、NEOブロックチェーン及びスマートコントラクトの他に、ストレージとしてipfs(InterPlanetary File System)を利用し、フロントエンドはNuxt.jsで構築しています。

仕組み

前述の通り全ソースコードはSanchoのgithubリポジトリ[15]にて公開していますので、ここではSanchoの肝となる機能についてのみ部分的にソースコードを追いながら解説していきます。

漫画のアップロード

ユーザーがSanchoに漫画をアップロードすると、その漫画のimageファイルはipfsに保存され、返り値として取得したファイルの所在を示すハッシュ値がNEOのスマートコントラクトに格納されます。この際、漫画のタイトルとipfsに保存したファイルのハッシュ値はJSON文字列に整形されコントラクトに渡されます。以下が画像ファイルをipfsに保存してからそのハッシュ値をNEOのコントラクトに保存するまでのコードになります。

for (let file of files) {
  let reader = new FileReader();
  reader.onload = (e) => {
    let buf = new Buffer(e.target.result)
    ipfs.files.add(buf, (err, file) => {
      if (err) {
        console.log(err);
      }
      uploadedImageHashes.push(file[0].hash);
      this.uploadedImageURLs.push('https://ipfs.io/ipfs/' + file[0].hash)
      if (files.length == uploadedImageHashes.length) {
        const json = JSON.stringify({ title: "hoge", imageHashes: uploadedImageHashes, hash: sha3_256(JSON.stringify({ title: "hoge", imageHashes: uploadedImageHashes})) })
        console.log(json)
        Neon.doInvoke({
          net: "http://127.0.0.1:30333",
          script: Neon.create.script({
            scriptHash: this.$store.state.scriptHash, // Scripthash for the contract
            operation: 'putData', // name of operation to perform.
            args: [u.str2hexstring(json)]
          }),
          account: new wallet.Account(this.$store.state.privateKey),
          gas: 1
        }).then(res => {
          console.log(res);
        }).catch(e => {
          console.log(e);
        });
      }
    });
  };
  reader.readAsArrayBuffer(file);
}

上記のNeon.doInvoke()でscriptHash(Ethereumでいうコントラクトアドレス)により指定されたコントラクトのputDataというoperationがinvokeされており、具体的には以下のmethodを実行しています。アップロードされた漫画の情報を持つJSON文字列をBOOKLISTというmapで保存しています。

def put_data(ctx, data):
    book_list = Get(ctx, BOOKLIST)
    if len(book_list) == 0:
        Put(ctx, BOOKLIST, data)
        return True
    result = concat(book_list, concat(b',', data))
    Put(ctx, BOOKLIST, result)
    return True

投票による作者の決定

ハッカソンの限られた時間の中での開発であったため、プリミティブな実装になっていますが、以下が投票とそれによって選ばれた作者が漫画の収益を引き出せるようにする実装になります。ユーザーはvoteメソッドを叩くことで適当なアドレスに一度だけ投票することができ、票を獲得した人はwithdrowメソッドによりコントラクトからnep5トークンを引き出すことができます。ここでnep5トークンを使用しているのは、NEOのコントラクトがEthereumとは異なり基軸通貨をコントラクトで保持して分配するようなことができないためです。

def vote(ctx, bhash, address):
    attachments = get_asset_attachments()  # [receiver, sender, neo, gas]

    Notify(bhash)
    Notify(address)

    if Get(ctx, concat(bhash, concat(b'-', attachments[1]))) != 0:
        return False

    put_vote(ctx, bhash, address)

    Put(ctx, concat(bhash, concat(b'-', attachments[1])), 1)

    return True

def get_vote(ctx, bhash):
    return Get(ctx, bhash)

def put_vote(ctx, bhash, address):
    vote_data = Get(ctx, bhash)
    if len(vote_data) != 0:
        vote_dict = Deserialize(vote_data)
    else:
        vote_dict = {}
    if has_key(vote_dict, address):
        vote_dict[address] += 1
    else:
        vote_dict[address] = 1
    Put(ctx, bhash, Serialize(vote_dict))
    return True

def withdraw(ctx, bhash):
    Notify(bhash)
    attachments = get_asset_attachments()
    vote_data = Get(ctx, bhash)
    if len(vote_data) != 0:
        vote_dict = Deserialize(Get(ctx, bhash))
    else:
        return False
    raddress = reliable_address(vote_dict)
    vote_count = vote_dict[raddress]
    Notify(raddress)
    Notify(attachments[1])
    Notify(vote_count)
    if vote_count > 0:
        transfer(ctx, OWNER, raddress, 100)
        return True
    return False

詰まったポイント

言語ごとのdocumentのばらつき

各スマートコントラクト対応言語によりサンプルコードの数であったりdocumentの充実度に差があるので、C#で書かれている処理をどうすればPythonで実装できるのかわからないことがあり、結局NEOのdeveloperの方に直接聞いて助けていただきました。 対応言語が多いとこういう弊害が発生するんですね…。

neon-jsが曲者

neon-jsはbalanceを取得するときにneoscanのapiを叩いているので、privatenetで開発するときはprivatenetのneoscanを動かす必要があるのですが、最初そのことに気づかず困惑しました。またtestnetのneoscanのendpointとしてハードコードされている箇所が間違っていたりして、balanceを取得するのにも一苦労しました。総じてdocumentの通りにやっても動かないことがしばしばだったので適宜オブジェクトの中身をひっくり返して確認する必要がありました…。

docker-composeで環境構築したときにneo-privnetとneoscanが上手く連携できなかった

これは単純にgithubのREADMEをちゃんと読んでいなかったがために詰まっただけなのですが、 docker-composeでneoscanのコンテナがneo-privnetという名前でNEOのprivatenetのendpointを解決しようとしていて、neoscanのapiが叩けませんでした。実際にはlocalhostを指していたのでREADMEに書かれている通り127.0.0.1 neo-privnetという一行をhostsファイルに書き足すと動きました。READMEをちゃんと読みます。以下該当箇所抜粋[15]

While you wait, add this line to your hosts file: 127.0.0.1 neo-privnet

That allows you to connect to the privnet NEO nodes with the URLs returned by the neo-scan container. If you’re using neo-python to connect to the privnet, you can use the standard configuration. 127.0.0.1:30333 will continue to work, for example.

終わりに

ハッカソンの時間の都合上、広告から収益を得て分配するまでの処理は実装できませんでしたが、なんとかdemoに耐えうる形に仕上げ、審査員の方々から2位をいただけたのはとても嬉しかったです。

1位を獲得したオーストラリアから参加されたチームとは点数の上では1点差だったのですが、本業でNEOのDappsを開発されているということもあり、スマートコントラクトとUIの連携が鮮やかで、圧倒的な経験値の差を見せつけられました。海外の開発者の本気度を感じましたし、とても勉強になりました。

こういった機会を設けてくださったNEO Smart Economyの皆様に、改めて感謝いたします。今後もNEOの開発を頑張りたいと思います。

参考文献