主网环境合约中测试时间戳的宽度底线(更新022)
大致概括下,这次测试mainnet的内容,就是上面说过cardano的合约当前时间没有“点”的感念,只有当前时间“范围”的概念。那么这个“范围”的最低处,就是时间宽度范围的最小值,这个是我这次测试重点。
先看合约代码
from opshin.prelude import *
from opshin.std.builtins import *
from opshin.ledger.interval import *
@dataclass
class Sell(PlutusData):
CONSTR_ID = 1
@dataclass()
class TimeRedeemer(PlutusData):
CONSTR_ID = 0 # 显式指定构造器 ID
reference_time: int
ChannelAction = Union[Sell,TimeRedeemer]
def validator(context: ScriptContext) -> None:
cha: ChannelAction = context.redeemer
assert isinstance(cha, TimeRedeemer), "Redeemer 错误"
ref_timestamp = cha.reference_time
# # 获取链上时间
tx_info = context.transaction
# # V3 的 valid_range 访问方式
lower_bound = tx_info.validity_range.lower_bound.limit
if isinstance(lower_bound, FinitePOSIXTime):
ledger_time = lower_bound.time
unlock_range = make_from(ref_timestamp)
assert ledger_time >= ref_timestamp, f"链上时间落后ledger_time:{ledger_time}"
else:
assert False, f"没有设置有效期下限ref_timestamp:{ref_timestamp} , lower_bound:{lower_bound}"
这里的 ledger_time是 合约中获取的执行当时的时间戳范围的最低点,ref_timestamp 是 我的输入时间点。
再看调用合约代码
import os
import time
from pycardano import *
# 1. 初始化环境
network = Network.MAINNET
blockfrost_key = "输入你的blockfrost_key "
# 建议显式指定 base_url
ctx = BlockFrostChainContext(blockfrost_key, network=network)
# 2. 关键:同步链上“真实”时间
# 直接获取最新块,确保我们拿到的 Slot 和 Time 是匹配的“真值”
latest_block = ctx.api.block_latest()
current_slot = ctx.last_block_slot
# 这是节点认定的 POSIX 秒,转为毫秒
print(latest_block)
ledger_truth_ms = latest_block.time * 1000
print(f"--- 链上同步 ---")
print(f"当前 Slot: {current_slot}")
print(f"对应时间: {ledger_truth_ms} ms")
# 3. 准备地址和密钥 (请替换为你自己的)
psk = PaymentSigningKey.load(" 。。。。你的钱包私钥目录/timstamp1.skey")
pvk = PaymentVerificationKey.load(" 。。。。你的钱包公钥目录/timstamp1.vkey")
my_addr = Address(pvk.hash(), network=network)
# 加载并计算合约地址
with open("。。。。你的合约文件目录/script.cbor", "r") as f:
script_hex = f.read().strip()
v3_script = PlutusV3Script(bytes.fromhex(script_hex))
script_hash = plutus_script_hash(v3_script)
# 修改点3
#network = Network.TESTNET
network = Network.MAINNET
script_addr = Address(script_hash, network=network)
from dataclasses import dataclass
@dataclass
class TimeRedeemer(PlutusData):
CONSTR_ID = 0
reference_time: int
redeemer_data = TimeRedeemer(reference_time=ledger_truth_ms)
print(f"你的地址: {my_addr}")
print(f"合约地址: {script_addr}")
# --- 2. 第一阶段:锁定资金 (Locking) ---
def lock_ada(amount_ada: int):
print(f"\n[阶段 1] 正在向合约转入 {amount_ada} ADA...")
builder = TransactionBuilder(ctx)
builder.add_input_address(my_addr)
builder.add_output(TransactionOutput(script_addr, amount_ada * 1_000_000))
signed_tx = builder.build_and_sign([psk], change_address=my_addr)
tx_id = ctx.submit_tx(signed_tx)
print(f"锁定交易已提交! TxID: {tx_id}")
return tx_id
# --- 3. 第二阶段:赎回资金 (Spending/Calling Contract) ---
def unlock_ada():
print(f"\n[阶段 2] 正在准备调用合约赎回资金...")
latest_block = ctx.api.block_latest()
current_slot = ctx.last_block_slot
ledger_truth_ms = latest_block.time * 1000
# 3.2 查找合约地址上的 UTXO
script_utxos = ctx.utxos(str(script_addr))
if not script_utxos:
print("错误:合约地址上没有发现 UTXO。")
return
# 选择一个 UTXO 进行花费 (这里假设选第一个)
target_utxo = script_utxos[0]
print(target_utxo)
# 3.3 构建赎回交易
builder = TransactionBuilder(ctx)
# mainnet 成功记录
# 【21分钟】成功,reference_time_for_contract = ledger_truth_ms - 1300_000
# 【16分钟】成功,reference_time_for_contract = ledger_truth_ms - 1000_000
# mainnet【10分钟】失败
#reference_time_for_contract = ledger_truth_ms - 600_000
# mainnet【13分钟】失败
#reference_time_for_contract = ledger_truth_ms - 800_000
# 成功 17分钟
reference_time_for_contract = ledger_truth_ms - 1000_000
# 失败 13分钟
# reference_time_for_contract = ledger_truth_ms - 800_000
# 主网 800 , 80 , 8(测试3个档次)
current_time = int(time.time())
from datetime import datetime
dt0 = datetime.fromtimestamp(reference_time_for_contract / 1000)
dt1 = datetime.fromtimestamp(ledger_truth_ms / 1000)
dt2 = datetime.fromtimestamp(current_time )
print(f" build reference_time_for_contract time : {dt0}")
print(f" build ledger time : {dt1}")
print(f" build current_time : {dt2}")
redeemer = Redeemer(TimeRedeemer(reference_time_for_contract))
# 必须指定脚本、Redeemer 以及这是 PlutusV3 逻辑
builder.add_script_input(
target_utxo,
script=v3_script,
redeemer=redeemer
)
# 设置时间有效期 (必须对齐)
builder.invalid_before = current_slot
builder.invalid_hereafter = current_slot + 600
# 将钱领回到自己的地址,留出约 1 ADA 做手续费
builder.add_input_address(my_addr)
try:
# build 会自动处理平衡,如果时间没对齐,这里会抛出异常
signed_tx = builder.build_and_sign([psk], change_address=my_addr)
# 正式提交
tx_id = ctx.submit_tx(signed_tx.to_cbor_hex())
print(f"合约调用成功! 资金已赎回。TxID: {tx_id}")
except Exception as e:
print(f"合约执行失败: {e}")
if __name__ == "__main__":
# 执行阶段 1
lock_tx = lock_ada(2) # 锁定 3 ADA
print("等待交易上链确认 (约 60-90 秒)...")
while True:
# 轮询检查合约地址是否有刚才那笔交易的 UTXO
utxos = ctx.utxos(str(script_addr))
if any(u.input.transaction_id.payload.hex() == lock_tx for u in utxos):
print("资金已到账,开始执行解锁合约。")
break
time.sleep(10)
# 执行阶段 2
unlock_ada()
测试效果是,如果输入时间没有把当前时间点减够,那么就会报错Namespace(EvaluationFailure=Namespace(ScriptFailures=Namespace()))。如果减够时间了,例如20分钟,那么久合约执行成功。有测试减13分钟也会失败。
所以,在cardano智能合约里判断时间戳一定要预留好足够的时间。哪怕是mainnet

