Solana Tx解体真書~Solscanを日本一のしつこさで解説してみる~前編

 大層なお題が付いていますが、元ネタは↓です。
 昔はぶ厚いゲーム攻略本がたくさん売られていましたが、自分はそういったものが大好物で、隅から隅まで眺めているだけで幸せな子供でした。


 さて本題ですが、DeFiBot運用のため日々コードを読んでいると、コードを読むまでは知りようもなかった事柄が多く存在します。
 その中でもSolscanなどのTxのエクスプローラーは、プログラミングに明るくない人でも普段からよく目にするサイトだと思います。
 しかし、こんな経験はないでしょうか?

Twitterで日々あがってくるTxのスナショ。
「フムフム……ナルホドネ」←ナニモワカッテナイ。 
したり顔をしつつも、リプライを見ると、あーだこーだと反応する人たち。
「え、みんなこれ理解できてるの?」

 そんな焦り、覚えたことありますよね?
 私はあります。
 
というか、この界隈では前提知識や新情報が多すぎて、今でも毎日焦らされてます。 
 しかし大丈夫。このnoteを読めば、少なくともSolanaのTxに関してはすべて完璧に理解できるはずです。
 え?Etherscan?シラネ


1. まずは前提用語の解説

 さて、どこかから拾ってきたTxでも貼って解説を始めればいいのですが、トランザクションの詳細を解説するには、Solanaの仕組みに関する用語にもある程度触れておかなければなりません。
 なぜなら、トランザクションとは何かをひとことで表現すると、
「オンチェーンに送った命令がどう実行されたか、その処理を追うもの」
 
に他ならないからです。まぁこれはTxという用語自体の確たる定義うんぬんかんぬんではなく、Solscanの”Transaction Details”のページをそう捉えてくださいね、という話です。

 そして、このSolanaチェーンに命令を送り、実行させるためには必要となるモノが色々とあるのでそれらを各種説明したいと思います。

Program
 他チェーンでいうスマートコントラクトのこと。
 Solanaが高速に処理を行うため、Program自体は参照するためのデータを保持できないという縛りがある。
 ETH系では自動販売機に例えられることが多いですが、Solanaでは若干異なる点もあります。

Account
 オンチェーン上に存在するデータを入れた箱。
 Programが処理をする際には、各種参照するデータが必要なので、これらを渡す必要がある。 

 Solanaで特に重要となるこの二つをすごーくざっくり説明しました。
「Solanaが高速に処理を行うため、Program自体は参照するためのデータを保持できないという縛りがある」の部分があまりぴんとこない方がいらっしゃるかもしれませんので、例をあげてみましょう。

 1 + 1 = 2
 こんな処理をするProgramがあったとします。
 Program単体がオンチェーン上で保持できるのは、
   +   =
 これだけです。
 これは足し算のために必要な左辺(どこからそのデータを参照するか)も、計算結果である右辺も(どこへそのデータを渡すか)、Accountとして渡す必要があることを意味します。
 なぜこのような縛りがあるかというと、左辺にどんな値が必要なのかわからないのに、考えられるすべてパターンのデータをProgramに持たせていると、Program自体が膨れ上がってしまいます。
 そこで、その都度必要なデータを必要な分だけAccountとして渡してあげることで効率化を図り、Solanaの速度向上にひと役買っているというわけです。
 まぁその分開発者の負担は増えるんですが……(オゲェー)

 どうでしょうか?
 プログラムもアカウントも、日常で私たちがよく使う言葉ですが、ニュアンスが若干異なります。というか、先入観を捨てて全然別の言葉と捉えて頂いた方がスムーズに理解できるはずです。
 ここで注意して欲しいのは、ProgramもAccountの一種だという点です。
 Program(コントラクト)はAccountを受け取り、そのデータを基になんらかの処理をするものですが、そのProgramの中身、つまりプログラミングコードも「データ」であることには変わりありません。上記でいうと、「+」と「=」もデータだよねってことです。
 まぁ同じものってわけでもないので、ここではAccountが適用される範囲というのはかなり広いんだなぁ、とゆるく認識して頂ければ結構です。

 さらにこの2種から派生する、Txを理解するために必要な用語を挙げます。

Instruction
 命令。言葉通り、Programに命令(Instruction)を送り、データ(Account)を添付して渡す。
 すなわちInstruction = 処理に必要なAccountのリスト + Accountも知らないようなデータ(例:Swapするinput量など)。
 このInstructonをTxの中に入れて送信します。Txには複数個のInstructionを入れることもできるので、TxはInstructionの塊と捉えることもできます。

Instructionのイメージ図

CPI
 Cross-Program Invocations。直訳するとクロスプログラム呼び出し。Invocationsの部分を上記のInstrctionに置き換えても特に問題はないかと。つまり、Programの中で他のProgramを呼び出す、子受け、孫受けのInstructionのこと。

CPIイメージ図

PDA
 Program Derived Addresses。Program派生アドレス。
 こちらもAddressをAccountに置き換えても問題ないと思われる。Program自体が所有しているAccountのことで、秘密鍵が存在しない。所有者であるProgramが呼び出された際に、その内部でのみデータを書き換えられる。

PDAイメージ図

 
 この三種の用語をおさえるために、例もあげてみます。

山田くんがUSDC→GMTにスワップする
↓↓そのためには……
①STEPNの作ったDooarSwapProgramにInstructionを送る
↓↓
②DooarSwapがレートを基に何枚スワップできるか計算する
↓↓
③DooarSwapが内部でTokenProgramを呼び出し(CPI)、計算結果を添えてInstructionを送る
↓↓
④ー①TokenProgramが所有する山田くんのUSDCのAccount(PDA)とGMTのAccount(PDA)の中のデータを書き換える
↓↓
④ー②TokenProgramが所有するDooarSwapの流動性プールのUSDCとGMTのAccountから、山田くんが動かした分だけUSDCを増やして、GMTを減らす
↓↓
結果:山田くんのUSDCが減ってGMTが増える
   DooarSwapのPool内のGMTが減り、USDCが増える

 少し複雑に見えるかもしれませんが、着目してほしいのは2点です。

 ①山田君がTokenProgramを直接呼び出したわけではない。
 ②山田君の各種トークンのAccountはTokenProgramが所有している。

 ①に関しては、実際にSolscanのTxと照らし合わせた方が分かりやすいので、ここでは頭の片隅に入れておくだけで構いません。

 ②に関して深掘りしてみましょう。
「えっ?俺のトークンのアカウントなのに、俺が所有してないわけ?どゆこと?」
 と混乱したかもしれません。その疑問を解消するには、「権限」としての所有者と、「実行者」としての所有者を分けて考えます。
 つまり、あなたのトークンを操作する権限を持つのはあなただけですが、あなたのトークンを実際に操作できるのはTokenProgramだけです。

 名義人と不動産管理会社との関係に似ているかもしれません。あなたの不動産を実際に売買できるのが、専門の資格を持つ者に限られているとすると、あなたはその資格を持っているわけではありませんから、不動産管理会社に依頼しなければなりません。
 Solanaの世界では、「不動産を実際に売買できる資格を持つ者」にあてはまるのはTokenProgramだけになります。そして仲介業者の役目を果たすのが、上記では山田くんが直接Instructionで呼び出した、DooarSwapProgramになります。

 絡めて、PDAが秘密鍵を持たないという点についても、軽く説明しておきます。

 TokenProgram側の視点になって考えてみましょう。
 すべてのトークンのAccountは自身が管理しており、それを動かすことができるのも世界でただひとり、自分だけであるということになります。
 秘密鍵は必要でしょうか?
 自分にしか動かせないのだから、必要ありません。どのトークンが誰のものであるか、ということだけ把握しておけば
よいでしょう。
 
 つまり厳密には、あなたがPhantomに登録している公開鍵とその秘密鍵(ウォレットアドレス)は、トークンのAccountとはまるで別物です。
 ウォレットアドレスは、SystemProgramという別のProgramが管理しているAccountです。なのでTokenProgramの管轄外です。
 ただし、TokenProgramが所有しているトークンのAccount(PDA)は、SystemAccount(あなたのウォレット)と
で「所有者権限」といった形で紐づいているので、実際にユーザーが利用する際にはそれで事足ります。

 なかなかしんどいパートだったかもしれませんが、ここまで説明したことを理解できれば、この後に参照するSolscanのTxもより大きな解像度で理解できます。
 

2.SolscanのTransactionDetails

 
 早速ですが、適当にSolscanから拾ってきたTxを貼ります。どこの誰のものかは知りません。

概要は、

①JupiterAggregatorV3のProgramにInsturcitonを送り、 ②CykuraSwapを使用して、
③3.13SOLを99.86USDCにSwapしようとした 
④けれども、Slippageが大きかったのでやっぱやーめた

 となります。

 まずは最上部の3つのタブから見ていきましょう。
”OverView”がメインなので、他のふたつ”SOL Balance Change”と”Token Balance Change”に着目します。

”SOL Balance Change” 

 SOL(ネイティブコイン)の推移一覧のようです。
動いているのは一か所だけで、"Fee Payer"の属性が付いているAccountがマイナスになっていますね。

 -0.000005215となっているのが、トランザクションにかかったFee/手数料として差し引かれています。

 Fee以外にも、Rent(家賃)としてAccountにデータを保存しておくためのストレージ料が記入されることもあります。
 初めて使用するDEXやトークンを作る際、使用するSOLが普段より多いことに疑問を覚えたことがあると思います。
 その場合は、トランザクションにかかった手数料+家賃がまとめて差し引かれていることになります。手順はここでは紹介しませんが、家賃は手数料に比べて高い代わりに、使わなくなったAccountを閉じるときに入れていた家賃をそのまま回収することが可能です。
 
 他にも、この”SOL Balance Change”のタブでは、このTxでInsturctionを実行する際に関わったすべてのAccountを一覧で見ることができます。
 ”Writable” ”Signer” ”Fee Payer” ”Program”と四種の属性が確認できますが、”Writable”と”Signer”について解説します。

 まず”Writable”についてですが、前のパートで述べたようにAccountはデータを入れた箱です。
 ”Writable”は名前の通り、Account内のデータを書き換え可能であると示す属性であり、それはイコール、オンチェーンが記録しているデータとも言えます。
 データ自体は、Accountがひとつあたりに持てる容量内に収まっていればなんでもよく、TokenAccountであればそのトークンの数量ですが、別に今日の天気でも構いませんし、DEXであれば利用者数をただカウントしていくなんて使い方をしても構いません。(それが有益かどうかはおいといて)
 ただし、第三者が誰でも自由に書き換えることを許可するわけにもいきません。そこで登場するのが”Signer”属性です。

 PDAの項でも説明したように、すべてのTokenAccountはTokenProgramが管理しています。
 管理下にあるTokenAccount内の数量データを書き換える際には、”Signer”と書き換える予定のTokenAccountの所有者権限者が一致するかを確認することで、それが正当なInstructionであるかを判断しています。

 しかし、スクショを見てもらっても分る通り、”Writable”属性のAccountが、Swap依頼者が所有しているはずのAccountより遥かに多いことに気づくかもしれません。

 例えば、以下2つのAccountは確かにSwapのInstrutionを送信した”3tGqWvSz...…”のウォレットアドレスが所有するUSDCとWSOLトークンのAccountですが、それ以外はJupiter Aggregator V3の所有するものであったり、CykuraSwapの所有するものです。
 Ownerの欄を確認することで所有者が確認できます。

  • 2wLdGcGwDdGJz17AmvwQHY7WuBHbJ6R6KRfp7FmjJ8VX

  • DZXGkpxBUuTfuPWMWTaHoXFiYXFHQWi3gPniNMaatR8w

 それなのにどうしてこの2つ以外のAccount内のデータまで書き換え可能なのでしょうか?
 
 答えはそう複雑なものでもありません。Insturctionを受け取ったProgramはProgramで、独自に管理(所有)しているAccountが存在しており、その権限下にあるものは自由に書き換えることができる、または”Signer”として追加で署名してさらに次のProgramへInstructionを送ることができるからです。

  • EqLZKF1bwUWUzU5F21jDQMoK4HRx8wQpLt57jhP8LbKm

 このAccountであれば、OwnerがCykuraSwap(”Program”属性のcysPXA……のアドレス)となっていることが分かります。
 さらに言えば、このEqLZK...…のAccountは、

  • DGLx7iQF2crdB71aGn6nJ3yFZQhLfb1jBjzve6gHN9mL

  • 5j8ZbnFjFjrpMNnZL5PXn9t3Lyiktx15GZLPaYCy1aVT

 これらのUSDCとWSOLのトークンAccountの所有者権限を持つAccountでもあります。
 つまり”SOL Balance Change”の欄には記述されていませんが、子や孫請けのProgramを呼び出す際の追加の”Signer”が内部的には存在しているということですね。

 ちなみに、”EqLZKF......"もCykuraSwapProgramのPDAであり、その”EqLZKF......”自体もUSDCとWSOLトークンAccountである、TokenProgramのPDAの所有者権限を持っているということになります。少しややこしいですが。

 それともうひとつ、これはTokenProgram内で独自に適用されるルールですが、TokenAccountの数量は増える分には何も問題ないので、増やすだけなら”Signer”でなくても”Writable”属性としてAccountを渡すことができます。

"Token Balance Change"

 あまり説明することはないですが、Tokenと頭についているように、これらはTokenProgram内でのみのAccountに関するものです。
 ”SOL Balance Change”がそのTxすべてに関わったすべてのAccountの列挙であるのに対し、こちらはTokenProgram内というもっと狭い世界に限定したものということがわかります。

"Overview"

 さて、大変長らくお待たせしました。
 ようやくメインの"Oveview"に取り掛かることができます。
 と言いたいところですが、その前に”Overview”をさらに3つのパートに小分けしておきます。
 上からHeaderパート、Instruction Detailsパート、Program logパートです。
 順番に解説していきます。

"Overview"‐Header

各項目は以下の通り

  • Signature

  • Block

  • Timestamp

  • Result

  • Signer

  • Fee

  • Main Actions

  • Transaction Version

  • Previous Block Hash

  • Your Notes

 これにプラスしてAddress Lookup Table Account(s)という最近(2022/11/04時点)追加された項目が表示されることもあります。


  • Signature

 Txを識別するためのID。
 …と簡単に片づけてしまってもよいのですが、Signatureにはそれ以外にも重要な役割を持っています。

① 秘密鍵と公開鍵のペア(Keypair)を知っている人にしか生成できない。
② 受け取った側は、秘密鍵を知ることはできないが、そのSignatureがその公開鍵の正しい秘密鍵を知っている人によって作られたものであるところまでは確認できる。
③ Instruction内のデータが改ざんされていないか確認できる。

 こちらの記事の図が分かりやすかったので引用させていただきます。

引用元:https://microsoftou.com/whats-hash-function/

 Signatureに該当するのが上図③の部分、平文に該当するのがInstructionやら近々のBlockhash(=Previous Block Hash)やらをまるっと含めたMessageと呼ばれるデータ構造体になります。

 つまり、SignatureはTxを送る前に手持ちのデバイス上で生成されており、秘密鍵によって作成されてはいるが、秘密鍵そのものは手持ちのデバイスから外部へは一切流出していないことがわかります。
 「あなたがこの公開鍵の持ち主かわからないので秘密鍵を教えてください」などと言われたら、「じゃあ適当な文を秘密鍵でハッシュ化して送るので、僕の公開鍵で復号化してみてください。それで僕が持ち主だということはわかりますよね^^」と答えてあげましょう。

  • Block

Solana Explorer

 BlockとSlotの違いが自分もいまいちよくわかってませんが、GenesisBlockの#0からスタートして現在は453msのペースで生成されています。Block heightの方が少し少ないのは、弾かれたBlockは含まれていないからでしょう。
 ちなみに432000Blockを消化すると、Epochと呼ばれる期間がひとつ進みます。ひとつのBlock内で消化されるTxは大体500~3500とかなりバラつきがあります。

  • Timestamp

 特に述べることもないのですが、薄字の部分をクリックすると、日本時間(Local Time)に切り替えられることを知っている人は少ないはずです。なぜなら僕もたった今知ったからです。
 細かい事を言うと、個人的にこのTimestampがどのタイミングで捺されるのか気になるんですが、所詮秒単位なのでそこまで調べていません。

  • Result

 Success(成功)とFail(失敗)
 進捗であるFinalized/Confirmed、それからSolscan上で見たことはありませんがProcessedの3タイプあります。
 Processed→Confirmed→Finalizedの順に進みますが、どこからどこまでがProcessedでどこからConfirmedに切り替わるの? などと細かいことを気にしだしたらただでさえ少ない僕の毛量がFinalized(禿る)されてしまいますので、必要になったときに調べます。

  • Fee

 Solanaの手数料の基本額は固定で0.000005SOLです。ETHのように変動しません。上のスクショを見て「嘘言ってんじゃねーよ」とツッコミを入れたくなるかもしれませんが、本当です、信じてください。
 はみ出ている215の部分はCPU使用量に応じて加算できる上乗せFeeになります。(後編のCompute Budgetで詳しく解説します)
 半分はそのTxを処理したバリデータの収入になり、残り半分はSolanaによってバーンされます。
 ネットワークがビジーになると手数料が上がるとか、基本額自体も変えることができるだとか、マルチシグだとその人数分必要だとか、なんか色々あるようですが前ふたつはまだ起こってない(ビジー価格に関しては自信ない)、マルチシグもやったことないので知りません。(投げやり)

  • Main Actions

 ここまでお付き合い頂いた方には、この項目が何であるかを具体的に言い表すことができるはずです。
 そう、ここには*TokenProgramが行ったアクションのみが表示されます。
 Main Actions = Token Program Actionsだったわけです。
 ですので、Token Programが何も関わっていないTxの場合はこの項目は省略されます。
 ”Interact with Program ******"には、Token Programを呼び出した(CPIも含む)Programが表記されます。

*ネイティブSOLの場合は例外あり

  • Transaction Version

 LEGACYと0の2種類があります。LEGACYの方が旧式です。
 LEGACYと0の違いは、Address Lookup Table Account(s)=ALTの有無です。ALTはAccountの参照表として使用します。
 
 どうしてこんなものが必要になるかというと、Txを送信する際にはその全体のサイズが1232バイトを超えてはならないという制限があるためです。
 送信できるTxのデータの上限が決まっていないと、Txの中身によって送信スピードが遅かったり早かったりしてしまいますよね。そしてその上限が少なければ少ないほど、高速に送信できます。

 Accountのアドレスはひとつあたり32バイト必要ですので、1つのTxに持たせられるAccountの上限は単純計算で1232 / 32 = 38.5ですが、当然ながらAccount以外にもTx内にはSignature(64バイト)など他のデータも入れないといけないので、Programに渡すことのできるAccountはもっと少なくなります。
 使用するDEXにもよりますが、各Programが要求するAccount数は案外多いので、1度のTxで色んなDEXを通ってSwapするルートの場合、簡単にこの上限に達してしまいます。
 そこでたくさんのAccountの代わりにALTを渡すことで、間接的にAccountを参照してもらい、この上限を回避することができます。
 とはいってもその制限内に収まっていればそもそも問題ないので、Flash Loanなどの複雑なTxを活用するでもなければ気にする必要はありません。

  • Previous Block Hash

 Previousとあるので、ひとつ前のか?とも勘ぐったのですが、どうやらRecent(近々の) Block Hashのようです。

 内部的には、Txを送信する前に一度この近々のBlock Hash(どのBlockかを識別するID)を取得しており、Tx内に含めて送信しています。
 そうすることで、「いまさっきこのTxを作ったんだよ~」ということも暗に証明していることになります。
 「近々の」の範囲は、最新のBlockから150Block以内です。1Blockが0.5秒で進むと仮定すると、Txを作成して75秒以内に送らないと期限切れとなってしまうということですね。
 
 余談ですが、プログラミングのコード内ではRecent Block Hashとして存在し、Solana Explorerでも”Recent Blockhash”の表記なのに、なぜSolscanはわざわざ”Previous”表記にしたのでしょうか。Solscan最大の謎です。

  • Your Notes

 使ったことありません。Solscan固有の機能です。使用するにはSolscanにサインインしなければなりません。
 アカウントがあるとウォッチリストを作成できるようです。へー。

"Overview"‐Instruction Details

 おそらく、ここからが一番「なにやってんのかよーわからん」となるパートだと思われます。
 が、ここまであまりにもくどいくらい説明してきたので、後編へ分けたいと思います。

この記事が気に入ったらサポートをしてみませんか?