fix(dao): center starting_time in proposal_create validity window

For tiny-window test DAOs (preprod_test: 30s), the prior anchor
(valid_from = starting_time, invalid_from = starting_time + 30)
gave zero past-side slack. With koios block_time vs real chain
clock skewing ±60s on the public endpoint, hitting that window
is essentially a coin flip — the tx submits but never confirms
because the next block lands after invalid_from.

Centering keeps the validator-required width unchanged but moves
valid_from to starting_time - 15, so the chain now has 15s of
past-side slack to land the tx in a block. Same width, same
in-script time check (starting_time still ∈ [valid_from, invalid_from)),
better landing odds.

Sulkta-shape DAOs (1800s windows) are unaffected: 900s of slack
each side is plenty either way.
This commit is contained in:
Kayos 2026-05-07 20:19:27 -07:00
parent b7074fd81b
commit 5235a5d4c3

View file

@ -610,8 +610,20 @@ pub fn build_unsigned_proposal_create(
// `pvalidateProposalStartingTime` is satisfied because
// `starting_time_slot ∈ [valid_from, invalid_after - 1]` by
// construction.
staging = staging.valid_from_slot(args.starting_time_slot);
staging = staging.invalid_from_slot(args.starting_time_slot + max_width_slots);
// 2026-05-08: CENTER `starting_time_slot` inside the validity range
// (rather than putting it at the lower bound). Tiny test DAOs run on
// a 30-second create_proposal_time_range_max_width, and koios's tip
// endpoint lag vs. the actual node can swing ±60s. With
// valid_from = starting_time, the window only spans [now, now+30].
// If chain is even slightly past `now` when the tx lands, the tx
// expires. Centering gives [now-15, now+15] of slack — same width,
// same validator-bound, but the chain-now-at-block-time can drift
// ±15s without missing the window.
let half_width_slots = max_width_slots / 2;
let valid_from = args.starting_time_slot.saturating_sub(half_width_slots);
let invalid_from = valid_from + max_width_slots;
staging = staging.valid_from_slot(valid_from);
staging = staging.invalid_from_slot(invalid_from);
let proposer_pkh_arr: [u8; 28] = args
.proposer_pkh