讲师中心 微信公众号
AI工具推荐 视频效率加速

Starknet 交易批量处理程序

小静同学_3507

小静同学_3507

发布时间:2024-12-23 18:51:13

|

770人浏览过

|

来源于dev.to

转载

抽象的

本文介绍了 metacube 中使用的交易批处理程序,用于即时发送玩家赚取的 nft。它解释了批处理程序基于参与者的可扩展架构,并提供了 go 中的详细实现。

所有代码片段都可以在关联的 github 存储库中找到。

建筑学

image description

巴彻由两位主要演员组成:

  • builder 接收交易,将它们批处理成单个多调用交易,并将其发送给 sender actor。
  • 发送者使用适当的字段(随机数、最大费用等)完成交易,对其进行签名,将其发送到 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 是同一合约的一部分,因此有合约地址字段。 maxsize 字段是批次的最大大小,inchan 是将事务发送到 batcher 的通道。 failchan 用于发回发送失败的交易。

请注意,在这个实现中,后面所说的交易数据([]string)是一个由两个元素组成的数组:接收者地址和 nft id。

batcher 同时运行 builder 和 sender actor:

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 将交易数据对从 builder 发送到 sender。每个交易数据对都包含批量交易,每个交易的数据都嵌入其中。每笔交易的数据都与批量交易一起发送,以便可以将失败的交易发送回实例化 batcher 的实体。

建设者

让我们分析一下build actor。请注意,为了更好的可读性,代码已被简化(完整代码):

openclaw-gitcode-pr-monitor
openclaw-gitcode-pr-monitor

监控一个或多个 GitCode 仓库的 PR,通过 OpenClaw Gateway 自动执行 AI 审查,发布 PR 评论,并发送钉钉和企业微信通知。

下载
// 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)
        }
    }
}

runbuildactor 函数是 builder actor 的事件循环。它等待事务发送到批处理程序,并在批处理已满或达到超时时构建批处理事务。然后批量交易被发送到 sender actor。

发件人

现在让我们分析一下sender actor。请注意,为了更好的可读性,代码已被简化(完整代码):

// actual send actor event loop
func (b *batcher) runsendactor(txndatapairchan <-chan txndatapair) {
    oldnonce := new(felt.felt).setuint64(0)

    for {
        // receive the batch transaction
        txndatapair, ok := <-txndatapairchan
        if !ok {
            ...
        }
        txn := txndatapair.txn
        data := txndatapair.data

        // get the current nonce of the sender account
        nonce, err := b.accnt.nonce(
            context.background(),
            rpc.blockid{tag: "latest"},
            b.accnt.accountaddress,
        )
        if err != nil {
            ...
        }

        // it might happen that the nonce is not directly updated if another transaction was sent just before. therefore, we manually increment it to make sure this new transaction is sent with the correct nonce
        if nonce.cmp(oldnonce) <= 0 {
            nonce.add(oldnonce, new(felt.felt).setuint64(1))
        }

        txn.invoketxnv1.nonce = nonce

        // sign the transaction
        err = b.accnt.signinvoketransaction(
            context.background(),
            &txn.invoketxnv1,
        )
        if err != nil {
            ...
        }

        // send the transaction to the starknet network
        resp, err := b.accnt.addinvoketransaction(
            context.background(),
            &txn,
        )
        if err != nil {
            ...
        }

        // monitor the transaction status
    statusloop:
        for {
            // wait a bit before checking the status
            time.sleep(time.second * 5)

            // get the transaction status
            txstatus, err := b.accnt.gettransactionstatus(
                context.background(),
                resp.transactionhash,
            )
            if err != nil {
                ...
            }

            // check the execution status
            switch txstatus.executionstatus {
            case rpc.txnexecutionstatussucceeded:
                oldnonce = nonce
                break statusloop
            case rpc.txnexecutionstatusreverted:
                // a reverted transaction consumes the nonce
                oldnonce = nonce
                ...
                break statusloop
            default:
            }

            // check the finality status
            switch txstatus.finalitystatus {
            case rpc.txnstatus_received:
                continue
            case rpc.txnstatus_accepted_on_l2, rpc.txnstatus_accepted_on_l1:
                oldnonce = nonce
                break statusloop
            case rpc.txnstatus_rejected:
                ...
            default:
            }

            // loop until the transaction status is determined
        }
    }
}

runsendactor 函数是发送者 actor 的事件循环。它等待 builder 发送批量交易,对它们进行签名,将它们发送到 starknet 网络,并监控它们的状态。

关于费用估算的说明:可以在发送之前估算批量交易的费用成本。交易签名后可添加以下代码:

        fee, err := b.accnt.EstimateFee(
            context.Background(),
            []rpc.BroadcastTxn{txn},
            []rpc.SimulationFlag{},
            rpc.WithBlockTag("latest"),
        )
        if err != nil {
            ...
        }

这可能有助于确保在发送交易之前费用不会太高。如果估计费用高于预期,则可能还需要重新调整交易的最大费用字段。但请注意,当交易发生任何更改时,必须重新签名!

但是请注意,如果交易吞吐量相当高,您在估算费用时可能会遇到一些问题。这是因为当给定的交易刚刚被批准时,更新帐户的随机数会有一点延迟。因此,在估计下一笔交易的费用时,它可能会失败,认为随机数仍然是前一笔交易。因此,如果您仍然想估算费用,那么您可能需要在每笔交易之间提供一些睡眠以避免此类问题。

迈向通用批处理机

所提供的批处理程序专门用于从同一合约发送 nft。然而,该架构可以轻松适应发送任何类型的交易。

首先,发送到 batcher 的交易数据必须更加通用,因此包含更多信息。它们必须包含合约地址、入口点选择器和调用数据。然后必须调整 buildfunctioncall 函数来解析此信息。

还可以更进一步,将发件人帐户设为通用。这将需要更多的重构,因为必须针对每个发送者帐户对交易进行批处理。然而,它是可行的,并且可以实现更通用的批处理机。

但是,请记住,过早的优化是万恶之源。因此,如果您只需要发送 nft 或特定代币(例如 eth 或 strk),那么提供的批处理程序就足够了。

命令行工具

存储库代码可以用作 cli 工具来批量发送一堆 nft。该工具易于使用,阅读本文后您应该能够根据您的需要进行调整。请参阅 readme 了解更多信息。

结论

我希望这篇文章可以帮助您更好地了解metacube如何向其玩家发送nft。批处理程序是一个关键的基础设施组件,我们很高兴与社区分享它。如果您有任何问题或反馈,请随时发表评论或与我联系。感谢您的阅读!

热门AI工具

更多
超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

5739

2023.08.02

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

5187

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2288

2023.08.10

github中文官网入口 github中文版官网网页进入
github中文官网入口 github中文版官网网页进入

github中文官网入口https://docs.github.com/zh/get-started,GitHub 是一种基于云的平台,可在其中存储、共享并与他人一起编写代码。 通过将代码存储在GitHub 上的“存储库”中,你可以: “展示或共享”你的工作。 持续“跟踪和管理”对代码的更改。

8385

2026.01.21

GitHub官网入口版本汇总
GitHub官网入口版本汇总

本专题整合了GitHub入口版本地址汇总,阅读专题下面的文章了解更多详细内容。

818

2026.04.02

LLVM自定义Pass怎么写
LLVM自定义Pass怎么写

本专题聚焦LLVM自定义Pass开发,整理Pass类结构、run()方法、PreservedAnalyses、CMake构建、插件注册、-load-pass-plugin加载和测试用例编写流程。

100

2026.09.30

LLVM RISC-V参数配置教程
LLVM RISC-V参数配置教程

本专题介绍LLVM对RISC-V基础ISA和扩展的支持方式,涵盖RV32、RV64、标准扩展、实验性扩展、厂商扩展、-menable-experimental-extensions和版本差异。

100

2026.09.30

LLVM IR中间表示入门指南
LLVM IR中间表示入门指南

本专题整理LLVM IR的核心概念,包括中间表示作用、模块结构、函数、基本块、SSA形式、类型系统和常见语法,帮助新手理解LLVM编译流程中的关键层。

80

2026.09.30

PDF转图片方法
PDF转图片方法

需要把 PDF 页面用于上传、预览、分享或图片归档时,PDF 转图片方法专题整理 JPG/PNG 格式选择、逐页导出、清晰度设置、批量下载和结果检查等流程,帮助用户稳定完成 PDF 图片化处理。

60

2026.09.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
vscode手册
vscode手册

共0课时 | 0人学习

Git 教程
Git 教程

共21课时 | 8万人学习

Git版本控制工具
Git版本控制工具

共8课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn