Sui.

帖子

分享您的知识。

赏金+15

Xavier.eth.
Jun 27, 2025
专家问答

Sui 事务失败:为另一笔交易保留的对象

我在JsonRpcError尝试在 Sui 上执行交易时遇到了持久问题. 该错误表明对象是为另一个事务保留的,尽管我已经实现了延迟的顺序事务处理.

JsonRpcError: Failed to sign transaction by a quorum of validators because one or more of its objects is reserved for another transaction. Other transactions locking these objects:
- AV7coSQHWg5vN3S47xada6UiZGW54xxUNhRv1QUPqWK (stake 33.83)
    - 0x1c20f15cbe780ee7586a2df90c1ab70861ca77a15970bea8702a8cf97bd3eed9
    - 0x1c20f15cbe780ee7586a2df90c1ab70861ca77a15970bea8702a8cf97bd3eed9
    - 0x1c20f15cbe780ee7586a2df90c1ab70861ca77a15970bea8702a8cf97bd3eed9

我试过了: -顺序事务执行(等待前一笔交易完成) -增加了交易之间的 3 秒延迟

而且仍然会持续出现同样的错误.

使用 Sui RPC 提交交易. 同一个对象 ID 在锁定列表中多次出现. 即使仔细安排交易顺序,也会出现错误.

  1. 是什么导致对象被 “保留” 用于其他交易?
  2. 在交易中使用对象之前,如何正确检查对象是否可用? 3.是否有在 Sui 中处理对象锁的最佳实践?
  3. 这可能与交易终结时间有关吗?

以前有人遇到过这个问题吗?如果您对Sui交易中的适当对象管理有任何见解,将不胜感激!

  • Sui
  • Transaction Processing
  • Move
4
10
分享
评论
.

答案

10
harry phan.
Jun 30 2025, 15:00

如果您尝试同时运行两个事务(例如,在前一个事务完成之前启动一个),就会发生此错误. 如果您重试运行发布事务,但之前或同时未运行其他事务,则该事务应该会成功. 你可能还需要从水龙头中获取更多的气体(或者等一天——等待时代结束——物体才能解锁)

当您运行涉及您的地址拥有的对象(例如气体对象)的交易时,验证器会保留该对象的最新版本供其签署的交易使用. 如果你尝试同时运行两个交易,并且它们引用同一个对象,那么它们将相互竞争以获取验证者的签名. 幸运的是,其中一笔交易获胜并运行,而另一笔交易未能获得足够的签名. 在不愉快的情况下,两笔交易都可能无法获得足够的签名(如果两笔交易都获得了验证者三分之一以上的签名,则任何一笔交易都无法超过三分之二,这是阈值),这被称为模棱两可,从那时起,作为两笔交易输入的对象不能用于任何其他交易.

在时代结束时(它们大约持续一天——你可以在 https://suiexplorer.com 上查看下一个纪元变更的进度),所有锁都被释放,所以你可以再次使用这些物体,但是如果你自上次尝试以来没有发生过纪元变化,你将需要获得更多的汽油.

12
评论
.
0xduckmove.
Jun 30 2025, 07:04

嘿,你尝试的交易速度太快了,对象被锁定了.

尝试一次发送一个包含相同对象的交易,如果你发送两个交易,一些验证者可能会接受第一笔交易,有些验证者可能会接受第二笔交易,而你的对象将被锁定,因为每笔交易需要66.7%的验证器,而你可能只得到50%.

=> 等着纪元重置,很快就到了

查看更多:https://forums.sui.io/t/beginner-tutorial-error-when-deploying-simple-sui-package/44842

6
评论
.
Owen.
Owen4662
Jun 30 2025, 11:03

Sui 使用乐观并发控制,这意味着在事务中使用对象时会被锁定,直到该事务完成或到期.

即使你在两次事务之间等待 3 秒钟,如果前一个事务尚未完成,对象仍处于锁定状态. 这意味着交易仍处于待处理状态,并且持有对该对象的独占访问权限.


如何检查对象是否可用

使用 Sui RPC 方法:

sui_getObject

查看"status": "Locked"或的回复"owner": "locked".

请求示例:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "sui_getObject",
  "params": ["0x...object_id..."]
}

如果已锁定,请稍等片刻,稍后重试.

5
评论
.
MiniBob.
Jun 30 2025, 11:50

该错误表示您的事务使用的对象仍被先前尚未完成的事务锁定. 即使有延迟,在这些交易在链上完成之前,对象仍会被保留.

要修复:

  1. 在再次使用对象之前,请务必确认先前涉及对象的交易已完全完成.
  2. 通过 Sui RPC 检查物体状态,确保它们已解锁. 3.避免在相同的对象上发送并行或快速交易.
  3. 使用退避和最终性检查来实现重试,而不是固定延迟.

这种锁定可以防止更新冲突,这在 Sui 的对象模型中是正常的. JsonRpcError正确的排序和最终性确认是避免的关键.

4
评论
.
BigDev.
Aug 15 2025, 16:24

此错误表示您正在使用的对象仍被尚未完成的先前事务锁定. 即使有延迟,Sui仍会保留它们,直到连锁店确认完工.

要修复此问题,请确保先前使用这些对象的所有事务都已完全完成,然后再重复使用它们. 你可以通过 Sui RPC 查看他们的状态,看看他们是否已解锁. 避免发送涉及相同对象的多个或快速交易. 与其依赖固定延迟,不如使用带退避功能的重试,并在重试之前确认最终性.

这种锁定是 Sui 确保安全更新的方式的一部分,因此正确的排序和检查最终性是避免 jsonRpcError 的方法

2
评论
.
0xF1RTYB00B5.
Sep 18 2025, 16:25

That’s a great debugging question — I’ve run into this exact issue before when building staking flows on Sui. Let me answer in detail, step-by-step, from my perspective using Sui daily.


🔍 Why objects get “reserved” in Sui

In Sui, every transaction consumes specific object versions. Once you submit a transaction that references an object, the network puts a lock on that object version until the transaction is finalized (committed or fails).

So when you see:

JsonRpcError: Failed to sign transaction ... one or more of its objects is reserved

it means:

  • The object version you’re trying to use is still “in flight” (reserved by another PTB),
  • Or you’re re-using an outdated version without refreshing from the fullnode,
  • Or the validator quorum hasn’t yet reached finality for the first transaction.

That’s why the same objectId shows multiple times in the lock list — you’re trying to consume the same locked version repeatedly.


✅ How I check object availability before using

I always follow these steps:

  1. After each transaction, re-fetch fresh object refs before building the next transaction block.

    const fresh = await provider.getObject({
      id: "0x1c20f15cbe780ee...",
      options: { showOwner: true, showPreviousTransaction: true, showContent: true },
    });
    const version = fresh.data.version; // must use this version in the next PTB
    
  2. Wait for local execution / finality when submitting. Always request WaitForLocalExecution in your signAndExecuteTransactionBlock. This ensures the object is unlocked before you try to use it again.

    const res = await signer.signAndExecuteTransactionBlock({
      transactionBlock: tx,
      requestType: "WaitForLocalExecution",
    });
    
  3. Implement a retry with exponential backoff when conflicts occur. Even with sequencing, there are cases where the fullnode lags. I use a submitWithRetry wrapper (like the one I showed earlier) that catches Object version mismatch and retries after re-fetching the object.


⚡ Best practices for handling object locks

  • Never reuse old object references. Always fetch the latest state from the RPC before constructing each PTB.
  • Design with parallelism in mind. If multiple independent flows need the same object, shard state across multiple owned objects (avoid a single global shared object).
  • Batch operations when possible. Use a single TransactionBlock with multiple Move calls instead of sending sequential transactions that lock the same object repeatedly.
  • Add backoff + retries. I usually back off 100ms, 200ms, 400ms, 800ms … up to ~3s before giving up.
  • Use checkpoints/finality awareness. Remember: an object isn’t truly free until the checkpoint is committed.

⏱️ Relation to finality timing

Yes, this is directly related. Even after a transaction appears “executed”, the object may remain reserved until:

  • The local fullnode confirms execution,
  • The consensus checkpoint includes it,
  • The version updates propagate across validators.

That’s why adding a blind 3-second delay isn’t reliable — network conditions and validator lag can extend this. The right way is fetch-after-execution instead of sleeping.


🛠️ Example: Safe sequential staking

Here’s how I do it in code (simplified):

async function safeStake(signer, validatorAddr, stakeAmountMist, stakeObjectId) {
  let attempt = 0;
  while (attempt < 5) {
    try {
      // 1. Fetch latest object version
      const obj = await provider.getObject({ id: stakeObjectId, options: { showOwner: true } });
      const version = obj.data.version;

      // 2. Build PTB with fresh version
      const tx = new TransactionBlock();
      tx.moveCall({
        target: "0x2::staking::request_add_stake",
        arguments: [tx.pure(validatorAddr), tx.pure(stakeAmountMist.toString())],
        typeArguments: [],
      });

      // 3. Execute with finality wait
      const res = await signer.signAndExecuteTransactionBlock({
        transactionBlock: tx,
        requestType: "WaitForLocalExecution",
      });

      console.log("✅ success:", res.digest);
      return res;
    } catch (e) {
      if (e.message.includes("reserved") || e.message.includes("Object version mismatch")) {
        attempt++;
        const backoff = 200 * 2 ** attempt;
        console.warn(`conflict, retrying in ${backoff}ms`);
        await new Promise(r => setTimeout(r, backoff));
        continue;
      }
      throw e;
    }
  }
  throw new Error("failed after retries");
}

This ensures:

  • I always use the latest object version,
  • I wait for finality,
  • I retry on transient lock errors.

🧩 TL;DR

  • Cause: Object locks happen because an object version is still reserved by an in-flight transaction.
  • Fix: Always re-fetch latest object versions, wait for local execution, and retry on conflicts.
  • Best practice: Shard state, batch operations, and use retries/backoff rather than fixed sleeps.
  • Yes, finality timing matters. Blind delays won’t solve it — confirmation checks + re-fetching will.
0
评论
.

你知道答案吗?

请登录并分享。