帖子
分享您的知识。
+15
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 在锁定列表中多次出现. 即使仔细安排交易顺序,也会出现错误.
- 是什么导致对象被 “保留” 用于其他交易?
- 在交易中使用对象之前,如何正确检查对象是否可用? 3.是否有在 Sui 中处理对象锁的最佳实践?
- 这可能与交易终结时间有关吗?
以前有人遇到过这个问题吗?如果您对Sui交易中的适当对象管理有任何见解,将不胜感激!
- Sui
- Transaction Processing
- Move
答案
10如果您尝试同时运行两个事务(例如,在前一个事务完成之前启动一个),就会发生此错误. 如果您重试运行发布事务,但之前或同时未运行其他事务,则该事务应该会成功. 你可能还需要从水龙头中获取更多的气体(或者等一天——等待时代结束——物体才能解锁)
当您运行涉及您的地址拥有的对象(例如气体对象)的交易时,验证器会保留该对象的最新版本供其签署的交易使用. 如果你尝试同时运行两个交易,并且它们引用同一个对象,那么它们将相互竞争以获取验证者的签名. 幸运的是,其中一笔交易获胜并运行,而另一笔交易未能获得足够的签名. 在不愉快的情况下,两笔交易都可能无法获得足够的签名(如果两笔交易都获得了验证者三分之一以上的签名,则任何一笔交易都无法超过三分之二,这是阈值),这被称为模棱两可,从那时起,作为两笔交易输入的对象不能用于任何其他交易.
在时代结束时(它们大约持续一天——你可以在 https://suiexplorer.com 上查看下一个纪元变更的进度),所有锁都被释放,所以你可以再次使用这些物体,但是如果你自上次尝试以来没有发生过纪元变化,你将需要获得更多的汽油.
嘿,你尝试的交易速度太快了,对象被锁定了.
尝试一次发送一个包含相同对象的交易,如果你发送两个交易,一些验证者可能会接受第一笔交易,有些验证者可能会接受第二笔交易,而你的对象将被锁定,因为每笔交易需要66.7%的验证器,而你可能只得到50%.
=> 等着纪元重置,很快就到了
查看更多:https://forums.sui.io/t/beginner-tutorial-error-when-deploying-simple-sui-package/44842
Sui 使用乐观并发控制,这意味着在事务中使用对象时会被锁定,直到该事务完成或到期.
即使你在两次事务之间等待 3 秒钟,如果前一个事务尚未完成,对象仍处于锁定状态. 这意味着交易仍处于待处理状态,并且持有对该对象的独占访问权限.
如何检查对象是否可用
使用 Sui RPC 方法:
sui_getObject
查看"status": "Locked"
或的回复"owner": "locked"
.
请求示例:
{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_getObject",
"params": ["0x...object_id..."]
}
如果已锁定,请稍等片刻,稍后重试.
该错误表示您的事务使用的对象仍被先前尚未完成的事务锁定. 即使有延迟,在这些交易在链上完成之前,对象仍会被保留.
要修复:
- 在再次使用对象之前,请务必确认先前涉及对象的交易已完全完成.
- 通过 Sui RPC 检查物体状态,确保它们已解锁. 3.避免在相同的对象上发送并行或快速交易.
- 使用退避和最终性检查来实现重试,而不是固定延迟.
这种锁定可以防止更新冲突,这在 Sui 的对象模型中是正常的. JsonRpcError
正确的排序和最终性确认是避免的关键.
此错误表示您正在使用的对象仍被尚未完成的先前事务锁定. 即使有延迟,Sui仍会保留它们,直到连锁店确认完工.
要修复此问题,请确保先前使用这些对象的所有事务都已完全完成,然后再重复使用它们. 你可以通过 Sui RPC 查看他们的状态,看看他们是否已解锁. 避免发送涉及相同对象的多个或快速交易. 与其依赖固定延迟,不如使用带退避功能的重试,并在重试之前确认最终性.
这种锁定是 Sui 确保安全更新的方式的一部分,因此正确的排序和检查最终性是避免 jsonRpcError 的方法
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:
-
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
-
Wait for local execution / finality when submitting. Always request
WaitForLocalExecution
in yoursignAndExecuteTransactionBlock
. This ensures the object is unlocked before you try to use it again.const res = await signer.signAndExecuteTransactionBlock({ transactionBlock: tx, requestType: "WaitForLocalExecution", });
-
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 catchesObject 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.
你知道答案吗?
请登录并分享。
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.