GetAccessPassCommand::execute (get.rs:26-38) returns the 0.0.0.0 pass whenever that account exists, and falls back to the exact-IP PDA only when it is absent. It never considers whether the pass it returns is usable.
So a payer holding an epoch-stale 0.0.0.0 pass and a valid prepaid pass at their exact IP is turned away: doublezero connect ibrl gets the stale dynamic pass, the last_access_epoch >= epoch check fails, and it prints Unable to find a valid AccessPass — even though processors/user/create_core.rs accepts either PDA and would have taken the exact-IP one.
An EdgeSeat pass is a realistic source of the stale half: multicast is not epoch-gated (epoch_allows_connection / UserType::is_epoch_gated), so nothing keeps its last_access_epoch current.
The fix belongs in GetAccessPassCommand, not in a caller's pre-flight. CreateUserCommand (user/create.rs:36-38) resolves the accesspass account through the same command, so whatever resolution a pre-flight trusts has to be the resolution that picks the PDA the transaction sends — otherwise the check and the transaction disagree. That means the command needs an epoch read so it can evaluate both candidates and prefer one that passes the epoch check.
Reported by @juan-malbeclabs in review of #4230, which does not change this behavior.
GetAccessPassCommand::execute(get.rs:26-38) returns the0.0.0.0pass whenever that account exists, and falls back to the exact-IP PDA only when it is absent. It never considers whether the pass it returns is usable.So a payer holding an epoch-stale
0.0.0.0pass and a valid prepaid pass at their exact IP is turned away:doublezero connect ibrlgets the stale dynamic pass, thelast_access_epoch >= epochcheck fails, and it printsUnable to find a valid AccessPass— even thoughprocessors/user/create_core.rsaccepts either PDA and would have taken the exact-IP one.An
EdgeSeatpass is a realistic source of the stale half: multicast is not epoch-gated (epoch_allows_connection/UserType::is_epoch_gated), so nothing keeps itslast_access_epochcurrent.The fix belongs in
GetAccessPassCommand, not in a caller's pre-flight.CreateUserCommand(user/create.rs:36-38) resolves the accesspass account through the same command, so whatever resolution a pre-flight trusts has to be the resolution that picks the PDA the transaction sends — otherwise the check and the transaction disagree. That means the command needs an epoch read so it can evaluate both candidates and prefer one that passes the epoch check.Reported by @juan-malbeclabs in review of #4230, which does not change this behavior.