fix(mcp): clamp vote validity_upper to voting_end_slot when range overshoots
Same H-2-class issue as the advance Draft→VotingReady clamp. The vote MCP tool computed validity_upper = tip_slot + 1799 then errored if it overshot voting_end_check. On 30-min Sulkta-shape DAOs the voting window is 30 min wide, and the moment chain time crosses into the voting window the default 1799-slot upper bound lands ~30 min past tip — already past voting_end if voting_start was a few minutes ago. Now: when default upper would overshoot, clamp validity_upper_slot to voting_end_slot. Reject if remaining slots ≤ 5 (chain has no room to include). Caught 2026-05-08 trying to vote on preprod_test2 proposal #1 during a clean voting window — tx_upper landed 135s past voting_end. Clamp lets the vote tx fit. Cosign builder still has the same raw tip_slot pattern; deferred since cosign also requires within-Draft semantics and we don't have a multi-stake test yet to exercise it.
This commit is contained in:
parent
a485a6f0bf
commit
0c79231936
1 changed files with 26 additions and 8 deletions
|
|
@ -3083,9 +3083,8 @@ impl WalletService {
|
|||
.and_then(|t| t.get("abs_slot"))
|
||||
.and_then(|s| s.as_u64())
|
||||
.ok_or_else(|| format!("tip response missing abs_slot: {tip_resp}"))?;
|
||||
let validity_upper_slot = tip_slot
|
||||
let default_validity_upper_slot = tip_slot
|
||||
+ aldabra_dao::builder::proposal_create::VALIDITY_RANGE_SLOTS;
|
||||
let validity_upper_ms = slot_to_posix_ms(cfg.network, validity_upper_slot)?;
|
||||
let tx_lower_ms = slot_to_posix_ms(cfg.network, tip_slot)?;
|
||||
|
||||
// AUDIT-2026-05-06 H-3 fix: validator (Proposal/Scripts.hs PVote
|
||||
|
|
@ -3096,6 +3095,13 @@ impl WalletService {
|
|||
// "too early or invalid" script error. Catch lb-vs-voting_start
|
||||
// here too.
|
||||
//
|
||||
// 2026-05-08 follow-up: when default validity_upper would
|
||||
// overshoot voting_end (e.g. 30-min Sulkta-shape windows where
|
||||
// the 1799-slot validity range starting from current tip lands
|
||||
// past voting_end), clamp validity_upper_slot to voting_end_slot
|
||||
// so the range fits inside the voting window. Same trick the
|
||||
// proposal_advance Draft→VotingReady clamp uses.
|
||||
//
|
||||
// Read from prop_datum (target.datum was moved to prop_datum at L2636).
|
||||
let voting_start_check = prop_datum.starting_time
|
||||
+ prop_datum.timing_config.draft_time;
|
||||
|
|
@ -3108,12 +3114,24 @@ impl WalletService {
|
|||
voting_start_check.saturating_sub(tx_lower_ms)
|
||||
));
|
||||
}
|
||||
if validity_upper_ms > voting_end_check {
|
||||
return Err(format!(
|
||||
"tx upper bound {validity_upper_ms} ms is after voting window end {voting_end_check} ms \
|
||||
— voting closed for proposal #{proposal_id}"
|
||||
));
|
||||
}
|
||||
let voting_end_slot = posix_ms_to_slot(cfg.network, voting_end_check)?;
|
||||
let validity_upper_slot = if voting_end_slot < default_validity_upper_slot {
|
||||
// Clamp to voting_end. Reject if remaining slots are too narrow
|
||||
// to include the tx (≤ 5 slots is the same threshold the
|
||||
// advance clamp uses).
|
||||
if voting_end_slot <= tip_slot + 5 {
|
||||
return Err(format!(
|
||||
"voting window has only {} slots remaining (voting_end_slot={voting_end_slot}, \
|
||||
tip_slot={tip_slot}) — too narrow to include the vote tx; voting period \
|
||||
effectively closed for proposal #{proposal_id}",
|
||||
voting_end_slot.saturating_sub(tip_slot),
|
||||
));
|
||||
}
|
||||
voting_end_slot
|
||||
} else {
|
||||
default_validity_upper_slot
|
||||
};
|
||||
let validity_upper_ms = slot_to_posix_ms(cfg.network, validity_upper_slot)?;
|
||||
|
||||
// Wallet utxos with H-5-style asset propagation.
|
||||
let wallet_utxos: Vec<DaoWalletUtxo> = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue