🍼

小規模Web3事業者が考えるクラウドアーキテクチャ

2023/12/21に公開

Web3プロジェクトを運営したい。でもお金がない。というプロジェクトを運営・請負した時に、私が現時点で採用しているクラウドアーキテクチャに関して書きます。

※間違いやイケてない箇所などあるかと存じます。その際は、ご指摘いただけると幸いです。

運用費用

そんなにアクセス数が多いプロダクトを運用したことがないのであまり参考にならないかもしれませんが、月々ワンコイン で運用できてます。
KMSのキー保管コストの$1 がベースでかかる費用で、後は無料枠からあふれた分が費用として発生するイメージです。

やりたいこと(大事にすること)

やりたいことを整理しておきます。

  • UX!UX!( ゚∀゚)o彡゚
  • ガスレス!( ゚∀゚)o彡゚
  • meta tx!!!!( ゚∀゚)o彡゚
  • (できるだけ)高スループット!( ゚∀゚)o彡゚
  • 安く!!!( ゚∀゚)o彡゚

構成

いきなりですが、以下の構成で運営しています。

がっつりAWSに依存を使っています。
簡単に書くと、サーバーレスアーキテクチャで構築しています。
我々のような弱小Web3事業者は、EC2やECSなんていうお金がかかるサービスなんてものは使用できません。却下です却下。はい。

主な機能

大まかに本システムは以下の機能を具備しています。

  1. ユーザからの要求受付機能
  2. ユーザからの要求に対して署名し、 tx としてRPC node に送信
  3. (Option)ユーザからの要求が失敗した場合の例外処理
  4. オンチェーンデータのindexing

やることはシンプルです。

1,2 に関してのシーケンス図だけ置いておきます。

各機能で行っていること

では、各機能で何をやっているかみていきましょう。

1. ユーザからの要求受付機能

ユーザからの要求[1] を受信し、DynamoDBに書込む処理です。
Dapps側でERC712 に準拠した署名を生成してAPI Gatewayに投げます。(これが一番めんd(ry
んで、サーバ側はその要求を念のためチェックという名の dry-run しておkだったらDynamoDBの受付けレコードに書き込みます。

https://eips.ethereum.org/EIPS/eip-712

UX のために少し工夫している点

通常、クライアント側では 送信した tx が confirmされるまで閲覧できません。Block chain や RPC node の込み具合によってUXが安定しないです。私が作成しているプロダクトでは Block chainへの書き込みを待たずしてサーバ上で受付けレコードを書いた瞬間から要求処理結果が閲覧できるようにしています。
これにより、Block chainやRPC node の速度に依存せずUXを担保することができます。
ただし、これはユーザの要求から、オフチェーン上で諸々の情報(たとえばtokenId)が決定的に導出できる場合に限ります。理由は、オンチェーンとオフチェーンで情報の整合性を取る必要があるからです。
例えば、mint 順に tokenId を連番で割り振るcontractとは相性悪いです。なぜなら、tx を発行してみるまで tokenId が決まらないからです。

2. ユーザからの要求を tx として送信

1 で受付けた レコードを dynamoDB から読取って処理します。
Nonce 値の整合性をとるため、tx sign処理は工夫が必要です。
本lambda はDynamoDBの条件付き書込みを用いて atomic lockを施して並列処理を禁止し、順番に Nonce を取得して処理するようにしてます。
https://blog.manabusakai.com/2016/07/lambda-atomic-lock/

tx はスループットを担保するために、Multicall や 自前の batch executorをコントラクト側に実装して呼び出しています。
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Multicall.sol

3. ユーザからの要求が失敗した場合の例外処理

念のため例外処理も入れています。ただ単に2をリトライ処理する程度のものなので説明は割愛します。

4. indexing処理

ユーザに見せる情報は決定的でありDynamoDBに書いているわけなので、それを以てindexing としても良いのですが、念のため Onchain の情報をverify します。また、送信したtx が実際に confirm されたかどうかもチェックした方が良いです。
ただ、とても重要でめんどい な処理です。資金が潤沢にあればCOOLに T〇e g〇aphを使ったり、自前でIndexer を立てたりとかできますが、我々にそのような予算はありませんARIMASEN!(AA略

方法として私が取っているのは2つ。

クライアント側で tx が confirm された block を取得して サーバに通知してもらう

Dapps側でtx の状態をチェックする。tx がconfirm されるのをチェックする用途では web 系のDappsでも行われていますが、indexing 目的でもやってしまいます。
実装としては、

  1. tx が confirmされるまで待つ
  2. confirm されたら DynamoDBからレコードを引くのに必要な情報と block number とをサーバに通知する。
  3. サーバ側で block number から logs を取得し、所望のeventを parse してDynamoDBに書込む

これはシンプルですが割とWorkします。サーバの負荷も軽減できます。Indexerを立てる必要もありません。しかし、お気づきかもしれませんが、直コンされると Indexing できません(ツ)。NFTのようなDapps 外部でtxが発生する系のサービスではIndexingできないですね。なので、利用できるケースは限られます。残念。

また、全ユーザがDapps経由で tx の confirmを pollingするためRPC URLの負荷は増えることになります。

event hook サービスを使う

もう1つは on chain event を listenしてevent として hook してくれるサービス系です。我々が使用しているのは Moralis の stream です。(ちなみに Alchemy が同じようなサービスを提供開始していたので使ってみたいと思っています。)
https://moralis.io/streams/

半年前くらいに無料枠が縮小されましたが、少々のevent なら十分戦えます。
event が増えてきつくなったら他のサービスに移行すればよいのです。はい。
注意したいのは、 Moralis stream API は(無料で利用する場合は) contract address の登録数にも制限があります。たくさんの event をhook したい場合に困ったりします。

なので、弱小web3 事業者の我々は entry point 用のcontract を用意して、そのcontract でイベントを発行しましょう。

これで contract address 1 つですべての event を hookできます。やったね。On chain からのイベントを hook するので我々の Dapps 経由でなくても Indexing が可能です。魔法のようですね!

lambda の処理としては、 indexing 対象の block number を受取って logを parseして dynamoDBにぶち込んでいるだけです。
event さえhook できれば単純な作業です。

結言

弱小web3 エンジニアの戦いは続く...(続

最近、cloudflare でも個人的なDappsを作ったので、次は cloudflare 編を書こうと思います。(いつか...)

脚注
  1. ERC712 などに基づいて生成した署名 ↩︎

  2. こちらも条件付き書き込みにより同じNonce値を取得できたときに失敗する ↩︎

Discussion