Edited at
EthereumDay 14

0xProtocolの事例から見る現状のDappsスケール手法

More than 1 year has passed since last update.

この記事は Ethereum Advent Calendar 2017 の14日目の記事です。

syroheiです。最近Qiita書けていないので、久々のポストです。Dapps開発の参考になれば幸いです。

Ethereumでは、ハードフォークを必要とする規模の大きいスケーリング手法の話が注目されがちです。一方で現在、既に存在しているDappsが負荷低減のために足元でどのような対応を行なっているのかについて、語られることは多くありません。

今回はDEXの事例を通して、Dappsを現状のEthereumネットワークでもある程度負荷低減させる手法についてご紹介します。


DEX(Decentralized Exchange)が注目される理由

最近、日本語で書かれたDEX(Decentralized Exchange:分散取引所)の話題や記事が増えてきています。なぜここまでDEXが注目を集めているのでしょうか。

現状の暗号通貨取引所のシステムは、取引所が管理するウォレットをユーザに提供しているパターンが多いと思われます。しかし、サービスの運営会社に資金をデポジットし取引するこの方法では、カウンターパーティーリスクを免れることができません。実際に多くの取引所が資金流出の被害にあっており、今後同様の事件が起きる可能性もあることから、大きな問題となっています。

一方でDEXは、第三者の関与なしでp2pトレードを可能にします。(DEXの概要についてはこちらの記事をご覧ください → 【DEXとは】イーサリアムベースの仮想通貨取引システム(分散型取引所)

この手法を使えば、中央集権的なウォレットに資金をデポジットする必要がないため、カウンターパーティリスクを無くす取引サービスとして注目を集めています。

オンチェーン実装によるシンプルなDEXの実装を想像してみると、オーダーを出すことや取引ごとにトランザクションが発生するために、Ethereumネットワークに過大な負荷をかけることが予想できます。これでは大量の取引を捌くことができません。ではDEXでは現在、負荷低減のためにどのような工夫を凝らしているのでしょうか?

DEXの一つであり、既にいくつかの工夫が実装されている0xプロジェクトを例にとり、コードを交えながらその工夫を見ていきます。


0x(シンポルZRX)プロジェクトとは

https://0xproject.com/

0xプロジェクトは、Ethereum上のスマートコントラクトで、DEXを構築することを目指すプロジェクトです。

0xでは、取引の時に必要となるオーダーブックのマッチングを、EthereumのBlockchainに記録せず署名検証のみ行います。これにより、今まで困難だったオーダーブックのマッチングを高速化させることができます。(ちなみに最終的な決済はオンチェーンで行います)


0xのオフチェーン処理について

0xはオフチェーン上で、オーダーブックのメイカーと、テイカーのメッセージに署名を送ります。そしてオンチェーンで、テイカーの署名が入ったトランザクションを決済(セトルメント)する処理を行なっています。これは0xのjsライブラリである0x.jsで実装しています。

0xの仕様では、下記のようなオーダーのメッセージフォーマットを用意します。

 const message = {

maker : Ethereum address of our Maker.
taker : Ethereum address of our Taker.
feeRecipient : Ethereum address of our Relayer (none for now).
makerTokenAddress: The token address the Maker is offering.
takerTokenAddress: The token address the Maker is requesting from the Taker.
exchangeContractAddress : The exchange.sol address.
salt: Random number to make the order (and therefore its hash) unique.
makerFee: How many ZRX the Maker will pay as a fee to the Relayer.
takerFee : How many ZRX the Taker will pay as a fee to the Relayer.
makerTokenAmount: The amount of token the Maker is offering.
takerTokenAmount: The amount of token the Maker is requesting from the Taker.
expirationUnixTimestampSec: When will the order expire (in unix time).
}

Maker側で署名を生成します。このときmessageとそのハッッシュorderHashの署名ecSignatureを含め、テイカーに送ります。

 const orderHash = ZeroEx.getOrderHashHex(message);

// Signing orderHash -> ecSignature
const ecSignature = await zeroEx.signOrderHashAsync(orderHash, makerAddress);

// Append signature to order
const signedOrder = {
...order,
ecSignature,
};

Taker側はMakerで署名されたsignedOrderを受け取ったら、自身の署名でEthereum-txを生成し、ブロードキャストされます。

この時点でマッチングは成立です。

 const txHash = await zeroEx.exchange.fillOrderAsync(signedOrder, fillTakerTokenAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);

const txReceipt = await zeroEx.awaitTransactionMinedAsync(txHash)

ブロードキャストされ、Ethereumブロックに入ると、セトルメントが完了します。


本当にスケールするのか?

0xの手法は、オフチェーンでオーダーブックをマッチングすることで取引を高速化し、オンチェーンで最終的にセトルメントするものですが、果たして本当にスケールするのでしょうか?

オフチェーンのマッチング処理については、普通の署名メッセージを送るだけであり、処理速度はかなり早いと思われますが、そのマッチングされた結果を実際に決済(セトルメント)する処理が別途必要になります。

現状のEthereumのトランザクションの処理性能はおおよそ 7~14tx/sほどで、ブロックに入れられるTransactonの量は限られています。

0xのコントラクトコードを見ると、実は一つのトランザクションに複数のセトルメント処理を実行可能な関数が用意されています。

これにより、ある程度まとまったオーダーマッチングを一つのトランザクションでセトルメントできるようになります。原理上では、マッチングした数の同数が一度にセトルメントできれば、システムとしてはスケールするように思えます。

     /// @dev Synchronously executes multiple fill orders in a single transaction.

/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
/// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
/// @param v Array ECDSA signature v parameters.
/// @param r Array of ECDSA signature r parameters.
/// @param s Array of ECDSA signature s parameters.
function batchFillOrders(
address[5][] orderAddresses,
uint[6][] orderValues,
uint[] fillTakerTokenAmounts,
bool shouldThrowOnInsufficientBalanceOrAllowance,
uint8[] v,
bytes32[] r,
bytes32[] s)
public
{
for (uint i = 0; i < orderAddresses.length; i++) {
fillOrder(
orderAddresses[i],
orderValues[i],
fillTakerTokenAmounts[i],
shouldThrowOnInsufficientBalanceOrAllowance,
v[i],
r[i],
s[i]
);
}
}

しかし、これだけでセトルメントがスケールするかという点については、そう簡単ではないと思います。

おそらく、勝手な予想ではありますが、既存の取引所は瞬間で1000-2000tx/s程度の取引をセトルメントしていると思われます。それと同等のパフォーマンスを出せるようにするには、Ethereumのガスをかなり低コストにしなければなりません。

なぜなら、現状のEthereum上で実際にfillOrderをセトルメントしたトランザクションはおよそ107570gasを消費しており(12月現在)、単純計算で、BlockGasLimit = 8,000,000 / 107570 = 約74.37 tx/block、1block約16秒と考えると4.6tx/s程度しかセトルメントできない計算になります。そして0xで実際に使われたトランザクションを見ると、かなり多くのgasが使われていることが分かります。

参考: fillOrder onchain settlement

https://etherscan.io/tx/0x9db216114f68ddd5b1a4b8e166cd0055df727ab513bca0318a35905ec874900b


成り行きオーダーが高価すぎる

また、かなり致命的な問題として、成り行きオーダーが非常に高価な処理である点があります。以下は0xによる成り行きオーダーの実際のトランザクションです。

参考 :fillOrdersUpTo onchain settlement

https://etherscan.io/tx/0x960287a6ef997094fb9f2740286537fc5a6a3c11070ac34ba9968df6862e88c4

成り行きオーダーを出した場合、860207gasを消費しており、これはBlockGasLimitの約10%に当たります。

また、同時にネットワーク手数料も0.018924554ether($13程度)かかっており、かなり高価であると言えるでしょう。


まとめ

オフチェーン技術がそこそこスケールするところまできているとはいえ、実際にサービスを運用するには、オンチェーンでのセトルメントが必要になります。これはDEXだけでなく、他のDappsについても同じような問題があると言えます。

結局のところ、Ethereum自体のスケーラビリティ向上が必要な段階がきているということですね。Plasmaによるコンピューティング処理のスケーリングや、Raidenを使ったStateChannel型のセトルメントなどを早期に実装していくことが重要なのかなと思います。

ちなみに、Raidenについては実際に触って見るのがいいと思います。 @tomohata さんが書いている記事が大変参考になります。

https://qiita.com/tomohata/items/21e33b0d68374090a697


さいごに

私たちは2017年10月に、DRIという組織を立ち上げました。現在メンバー4名と小さな組織ではありますが、Ethereumや分散型の技術を中心に、新たな経済圏を作りたいと考えています。第一弾としてRICOという、コントラクトだけで公平で透明性のあるICOを実施するOSSをリリースしていますが、それ以外にも新しい価値移動を実現する暗号通貨の開発に取り組んでいます。興味あるエンジニアの方は、Twitter(@syrohei)などでお気軽にお声がけいただけるとうれしいです!

また、DRIコミュニティSlackという、気軽に情報交換ができる場所も用意しております。今回取り上げたようなトピックや、Ethereumの最新ニュースについて会話していきたいと思っておりますので、ご興味のある方は是非ご参加ください!