Solana Atomic Arbitrageで日給1000円くらいになるまで
前記事
その後改良を重ねて日給1000円くらいになった+2月から忙しくなりしばらく開発が止まるので、備忘録がてらやったことを記していく。
まだ一撃と呼べるtxには出会っておらず、このままで出会えるのかどうかも不明。ただ1tx完結の美しさにはどハマリしており、txを通す快感に禁断症状でてる。
以下の紹介順は適当。上から順に実装していったわけではない。
プール状態の取得プロセスを分ける
元のプログラムは①全プールの状態取得→②裁定パスの算出→③tx送信が1セットになっており、この1連の流れを無限ループで回し続けるという運用になっていた。
プール数が少ないうちはこれでよかったけど、プール数が増えると①で2つ問題が生じた。
全件取得に時間がかかるので裁定パス算出の時点で既に状態が古い
RPCノードのレートリミットに引っかかる
①の処理を別プログラムに切り出して対応した。
レートリミットに引っかからない程度にプール状態を取得
プール状態は外に書き出す
②以降の処理では書き出されたものを読み込んで実行。その際に更新時間がn秒以内のもの〜みたいなプール状態が最新に近いものだけを使って裁定パスを算出するといった処理等も追加。
書き出しは普通にJSONファイルに書き出してる。本当はRedisとかそういうのにやった方がいいんだろうけど、面倒なのでファイル書き出しで対応した。そういう細かい最適化よりも先にやれることはたくさんあると言い聞かせる。
全体のリファクタリング
元のプログラムはあちこちでunwrapしてエラーを握りつぶしており、予期しない入力で落ちてしまうといった問題が頻発していた。
RustはExceptionが(原則)ないので、この辺りの処理をtry-catchで簡便に回避するというのがやりにくかった。
ということで、重い腰を上げてリファクタリングを実施した。
Rustの”安全”というのがどのように実現されているのかこの辺りで理解が深まった。
ハードコードされているコンフィグを外から変えられるようにするなど、今後の検証に必要になりそうな箇所に手を入れた。
めんどくさかったけど、今はちゃんとやっておいてよかったという気持ちになっている。
DEXの追加
Orca・Aldrin・Raydium(V4)を追加した。前記事でOrcaやAldrinは死んでいると勘違いしてたけど生きていた。可用プールのリストと状態取得を行うスクリプトを書いたら他は元々の実装をそのまま使えたのでよかった。
Raydiumも同様のスクリプトを書いて、価格算出関数とInstruction作成関数を書いてあげたら動いた(オンチェーンプログラムも同様)。
全部JupiterやDEXが提供しているSDKの中を見ながらRustに移植しているだけなのでそんなに難しいことはなかった。Jupiter SDKはv0.3(だっけ?)からminifiedされたものしか提供されていないので、breakpoint打ちながら処理途中の状態確認するのが面倒だった。古いやつと並べて実行してトラックした。
Raydiumのプールはofficialとunofficialがあった。その違いはよくわからなかったし、今もわかっていない。unofficialの数が非常に多いので、今はofficialのプールだけを対象にしている。多分unofficialプールの中から最近取引があるプールだけを対象にして取り出すようにしたらいいのかもな、とか思いながらもやっていない。
あとはLifinity、Raydium CLMM、Meteoraなどまだプール開拓の余地は大きい……めんど(ry
Address Lookup Tableの実装
Solanaのtxはサイズ制限がある。RaydiumやWhirlpoolはInstructionで参照するAccountが多い。なので、Whirlpool -> Raydium -> Raydiumみたいなパスを辿るtxにおいて `Transaction too large` といったエラーが出現して発覚した。
ちょっと調べるとAddress Lookup Table(以下ALT)という仕組みで回避ということがわかった。めんどくさかったが、必須っぽいので仕方なく実装した。
仕組みは単純でALT Accountのデータに参照したいAddressを入れておいて、txにはALTとそのALTの中で参照するAddressのインデックスを記述するだけ。
ALTを使用するにあたってVersioned Transactionに変更する必要があった。参照周りの記述はSolana SDKが中で良しなにやってくれるので、そこは楽で良かった。
ALTの作成においては、その仕組からするとグラフパーティショニングアルゴリズムやクラスタリングアルゴリズムを用いて、txで共起しやすいAddressが同じバケット(=テーブル)に入るように作るのがベストなんだろなと思った。
が、めんどくさかったので、とりあえず愚直に使っているアドレスを全部適当にぶち込んだ。
結論から言うとテーブル作成の最適化は優先度低いし、全部ぶちこむ必要もなかった感じがしている。しょっちゅう使うAddressはかなり限られているのでそれらだけを詰め込んだテーブルを用意すれば十分。テーブルに存在しないアカウントは、これもSolana SDKが良しなにtxに直に書き込んでくれる。
しかしめんどくさくてTableの作り直しはやっていないのである。ただ頻出するAddressを突っ込んだテーブルだけ追加で用意した(完)。
話は逸れるが、ATAやらALTやらでSOLのデポジットがちょくちょく必要になる。オンチェーンプログラムのデプロイにも8SOLくらいデポジットされた。これらのデポジットは当該Accountを閉じれば戻ってくるものの、ここまで開発するにあたって10~15SOLくらいは必要になってる。
DFS(幅優先探索)の実装
元プログラムの裁定パス検出はBFS(深さ優先探索)による全探索アルゴリズムが実装されていた。長いパスより短いパスの方がtxを通しやすい気がしたのでDFSに実装を変えた。ちゃんとA/Bテストやってないけど結構改善した気がする。
裁定パスの算出にはダイクストラ法やベルマンフォード法といった経路探索アルゴリズムで効率的に計算できるけど、現状これらを実装する予定はない。理由としては
めんどくさい
最短経路(最適裁定パス)はサクッと求まるけど、ある閾値以上の裁定パスを列挙するという使い方をしようとすると結局全探索になる気がする(たぶん)
漢は黙って全探索
やるとしたらメモ化や枝刈りで全探索の効率化かな。使用頻度に基づいたA*探索とか面白そうと思っている。
全探索は普通につおい。きつくなってきたらマシンパワーと並列化で殴るつもり。
自前RPCノードの検討
結果から言うと自前RPCノードは建ててない。以下はAWS上に建ててみて使ってみた感想。
cdkのテンプレがあったので用意自体は簡単だった
使い勝手はいいしできれば用意したいが、いかんせん必要スペックが高く維持費が高い
Edenなんとかなどの専用プロバイダーから借りたほうが安い
自前ノードで優位性を出すよりも先にできることは色々あるので、導入するとしてもそこら辺全部やりきってからかな
最速txを求めると自ずと自前ノード用意したくなる気がするが、最速txはかなりハードル高いと感じている。完全に想像でしかないけど、それなりの数のRPCノード用意して最も同期が進んでいる(?)ノードからリクエスト出すとかそういう力技が必要なんじゃないかなーと想像してる。いやわかんないけど。
自分のような弱botはコバンザメのように徘徊して、良い場所良いタイミングを定めて口をパクパクし続けるのが吉と現状で判断している。
そういう方向性だと自前ノードの優先度は低くなって、現状heliusやAlchemy使ってる。
TXパラメーターのチューニング
元プログラムはpreflightなしでconfirmation levelはconfirmedでtxを送っている。これが全然tx通らない。ほぼ鞘なくなっててrevertされる。
preflightあり+finalizedにしたらrevertされにくくなった(preflightの時点でrevertされてそもそもtxが送られないので当然)。現状tx送ったやつのうちだいたい半分くらいが成功して、もう半分がrevertされてる。
tx送ってrevertされてるやつはpreflight時点では鞘があったけど、実際に送ったときにはなくなっていたってやつだから、この割合が少なくなったらpreflightもなくす感じかなーと現状考えている。どうやったらなくなるのか、なくしたらtxがもっと通るのかは不明。
正直まだSolanaのconfirmation処理についてよくわかっていない。なんとなく感じているのは、ただ速くtx送るだけじゃなくて、validatorが取り込んでくれるタイミングを見計らって送るのが大事なんじゃないかと思っている。txは通るときはポンポンポンと通るし、通らないときは連続で失敗してたりするから。全く見当違いなことを言ってる可能性もあるので全く信用しないでほしい。
そこら辺の処理の仕組みは興味あるのでそのうちソース読む。
Priority feeの設定
ガス代多めに払ってtx通りやすくするってやつもやってみた。
めんどくさくてちゃんとA/Bテストやってないがあんまり効かなかった。
自分のレベルだとまだそういう域にいないということなのだと納得して、最低feeで継続中。
今後
書き出してみて認識したけどめんどくさくてやってない・はしょった箇所が結構あった。このあたりを詰めていくのが今後の作業になりそう。
現状、そんなに難しいことはやってないので、さらなる高みを目指すにはしっかり人間性を捨てていく必要があるんだなという認識でいる。
今はソースコードやらドキュメントやらが充実してるからこれくらいサクサク進むけど、もっと初期の頃はずっと大変だっただろうなと先人に感謝なのである。
この記事で書いている開発にあたって参考にしたレポジトリはこちら(ほぼraydium)。
皆さんも良きSolanaアビトラライフを!