ホームページ >バックエンド開発 >Golang >Starknet トランザクション バッチャー

Starknet トランザクション バッチャー

Linda Hamilton
Linda Hamiltonオリジナル
2024-12-31 17:54:14602ブラウズ

抽象的な

この記事では、プレイヤーが獲得した NFT を即座に送信するために Metacube で使用されるトランザクション バッチャーを紹介します。バッチャーのスケーラブルなアクターベースのアーキテクチャについて説明し、Go での詳細な実装を提供します。

すべてのコード スニペットは、関連する GitHub リポジトリで入手できます。

建築

A Starknet transactions batcher

バッチャーは 2 人の主要な俳優で構成されています:

  • ビルダーはトランザクションを受信し、それらを 1 つのマルチコール トランザクションにまとめて、Sender アクターに送信します。
  • 送信者は、適切なフィールド (ノンス、最大料金など) でトランザクションを完了し、署名して Starknet ネットワークに送信し、そのステータスを監視します。

このアクターの分離により、スケーラブルで効率的なバッチャーが可能になります。送信者がトランザクションを送信している間にビルダーがトランザクションを準備するため、継続的かつ効率的なトランザクション フローが可能になります。

実装

次の実装は Go に固有ですが、機能は同じであるため、概念は他の言語にも簡単に適応できます。

さらに、この実装は同じコントラクトからの NFT の送信に固有であることに注意してください。ただし、より一般的なアプローチについては、この記事の後半で説明します。

最後に、コードは Nethermind によって開発された starknet.go ライブラリに基づいています。

バッチャー

Batcher 自体から始めましょう:

type Batcher struct {
    accnt           *account.Account
    contractAddress *felt.Felt
    maxSize         int
    inChan          <-chan []string
    failChan        chan<- []string
}

アカウント (accnt) は NFT を保持するもので、NFT を転送するトランザクションに署名するために使用されます。これらの NFT は同じコントラクトの一部であるため、contractAddress フィールドになります。 maxSize フィールドはバッチの最大サイズで、inChan はトランザクションがバッチャーに送信されるチャネルです。 failedChan は、送信に失敗したトランザクションを送り返すために使用されます。

この実装では、後で呼び出されるトランザクション データ ([]string) は、受信者のアドレスと NFT ID の 2 つの要素の配列であることに注意してください。

Batcher は、Builder アクターと Sender アクターの両方を同時に実行します。

type TxnDataPair struct {
    Txn  rpc.BroadcastInvokev1Txn
    Data [][]string
}

func (b *Batcher) Run() {
    txnDataPairChan := make(chan TxnDataPair)

    go b.runBuildActor(txnDataPairChan)
    go b.runSendActor(txnDataPairChan)
}

定義されたチャネル txnDataPairChan は、トランザクション データ ペアをビルダーからセンダーに送信します。各トランザクション データのペアはバッチ トランザクションを構成し、各トランザクションのデータがバッチ トランザクションに埋め込まれます。各トランザクションのデータはバッチ トランザクションとともに送信されるため、失敗したトランザクションは Batcher をインスタンス化するエンティティに送り返すことができます。

ビルダー

Build アクターを分析してみましょう。読みやすくするためにコードが簡略化されていることに注意してください (完全なコード):

type Batcher struct {
    accnt           *account.Account
    contractAddress *felt.Felt
    maxSize         int
    inChan          <-chan []string
    failChan        chan<- []string
}

runBuildActor 関数は、Builder アクターのイベント ループです。トランザクションがバッチャーに送信されるのを待機し、バッチがいっぱいになるかタイムアウトに達すると、バッチ トランザクションを構築します。その後、バッチ トランザクションが Sender アクターに送信されます。

送信者

次に、Sender アクターを分析してみましょう。読みやすくするためにコードが簡略化されていることに注意してください (完全なコード):

type TxnDataPair struct {
    Txn  rpc.BroadcastInvokev1Txn
    Data [][]string
}

func (b *Batcher) Run() {
    txnDataPairChan := make(chan TxnDataPair)

    go b.runBuildActor(txnDataPairChan)
    go b.runSendActor(txnDataPairChan)
}

runSendActor 関数は、送信側アクターのイベント ループです。 Builder がバッチ トランザクションを送信するのを待ち、署名して Starknet ネットワークに送信し、ステータスを監視します。

手数料の見積もりに関するメモ: バッチ トランザクションを送信する前に、その手数料コストを見積もることができます。トランザクションの署名後に次のコードを追加できます:

// This function builds a function call from the transaction data.
func (b *Batcher) buildFunctionCall(data []string) (*rpc.FunctionCall, error) {
    // Parse the recipient address
    toAddressInFelt, err := utils.HexToFelt(data[0])
    if err != nil {
        ...
    }

    // Parse the NFT ID
    nftID, err := strconv.Atoi(data[1])
    if err != nil {
        ...
    }

    // The entry point is a standard ERC721 function
    // https://docs.openzeppelin.com/contracts-cairo/0.20.0/erc721
    return &rpc.FunctionCall{
        ContractAddress: b.contractAddress,
        EntryPointSelector: utils.GetSelectorFromNameFelt(
            "safe_transfer_from",
        ),
        Calldata: []*felt.Felt{
            b.accnt.AccountAddress, // from
            toAddressInFelt, // to
            new(felt.Felt).SetUint64(uint64(nftID)), // NFT ID
            new(felt.Felt).SetUint64(0), // data -> None
            new(felt.Felt).SetUint64(0), // extra data -> None
        },
    }, nil
}

// This function builds the batch transaction from the function calls.
func (b *Batcher) buildBatchTransaction(functionCalls []rpc.FunctionCall) (rpc.BroadcastInvokev1Txn, error) {
    // Format the calldata (i.e., the function calls)
    calldata, err := b.accnt.FmtCalldata(functionCalls)
    if err != nil {
        ...
    }

    return rpc.BroadcastInvokev1Txn{
        InvokeTxnV1: rpc.InvokeTxnV1{
            MaxFee:        new(felt.Felt).SetUint64(MAX_FEE),
            Version:       rpc.TransactionV1,
            Nonce:         new(felt.Felt).SetUint64(0), // Will be set by the send actor
            Type:          rpc.TransactionType_Invoke,
            SenderAddress: b.accnt.AccountAddress,
            Calldata:      calldata,
        },
    }, nil
}

// Actual Build actor event loop
func (b *Batcher) runBuildActor(txnDataPairChan chan<- TxnDataPair) {
    size := 0
    functionCalls := make([]rpc.FunctionCall, 0, b.maxSize)
    currentData := make([][]string, 0, b.maxSize)

    for {
        // Boolean to trigger the batch building
        trigger := false

        select {
        // Receive new transaction data
        case data, ok := <-b.inChan:
            if !ok {
                ...
            }

            functionCall, err := b.buildFunctionCall(data)
            if err != nil {
                ...
            }

            functionCalls = append(functionCalls, *functionCall)
            size++
            currentData = append(currentData, data)

            if size >= b.maxSize {
                // The batch is full, trigger the building
                trigger = true
            }

        // We don't want a smaller batch to wait indefinitely to be full, so we set a timeout to trigger the building even if the batch is not full
        case <-time.After(WAITING_TIME):
            if size > 0 {
                trigger = true
            }
        }

        if trigger {
            builtTxn, err := b.buildBatchTransaction(functionCalls)
            if err != nil {
                ...
            } else {
                // Send the batch transaction to the Sender
                txnDataPairChan <- TxnDataPair{
                    Txn:  builtTxn,
                    Data: currentData,
                }
            }

            // Reset variables
            size = 0
            functionCalls = make([]rpc.FunctionCall, 0, b.maxSize)
            currentData = make([][]string, 0, b.maxSize)
        }
    }
}

これは、トランザクションを送信する前に手数料が高すぎないことを確認するのに役立つ場合があります。推定手数料が予想よりも高い場合、推定手数料が予想よりも高い場合は、トランザクションの最大手数料フィールドを再調整する必要がある場合もあります。ただし、トランザクションに変更を加えた場合は、再度署名する必要があることに注意してください。

ただし、トランザクションのスループットが非常に高い場合、手数料を見積もるときに問題が発生する可能性があることに注意してください。これは、特定のトランザクションが承認されたばかりの場合、アカウントの nonce の更新に多少の遅れが生じるためです。したがって、次のトランザクションの手数料を見積もるときに、ノンスがまだ前のトランザクションであると考えて失敗する可能性があります。したがって、それでも手数料を見積もりたい場合は、そのような問題を避けるために、各トランザクションの間にある程度のスリープを設ける必要があるかもしれません。

汎用バッチャーに向けて

提示されたバッチャーは、同じコントラクトからの NFT の送信に特化しています。ただし、このアーキテクチャは、あらゆるタイプのトランザクションを送信するように簡単に適合させることができます。

まず、Batcher に送信されるトランザクション データはより汎用的である必要があり、したがってより多くの情報が含まれている必要があります。これらには、コントラクト アドレス、エントリ ポイント セレクター、および通話データが含まれている必要があります。この情報を解析するには、buildFunctionCall 関数を適合させる必要があります。

送信者アカウントを汎用にすることでさらに一歩進めることもできます。トランザクションは送信者アカウントごとにバッチ処理する必要があるため、これにはさらに多くのリファクタリングが必要になります。ただし、これは実現可能であり、より汎用性の高いバッチャーが可能になります。

ただし、時期尚早な最適化が諸悪の根源であることを忘れないでください。したがって、NFT または ETH や STRK などの特定のトークンを送信するだけの場合は、提示されたバッチャーで十分です。

CLIツール

リポジトリ コードは、大量の NFT をバッチで送信するための CLI ツールとして使用できます。このツールは使いやすいので、この記事を読んだ後はニーズに合わせて調整できるはずです。詳細については、README を参照してください。

結論

この記事が、Metacube がプレイヤーに NFT を送信する方法をよりよく理解するのに役立つことを願っています。バッチャーは重要なインフラストラクチャ コンポーネントであり、喜んでコミュニティと共有します。ご質問やフィードバックがございましたら、お気軽にコメントいただくか、私までご連絡ください。読んでいただきありがとうございます!

以上がStarknet トランザクション バッチャーの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。