Summary
Group invite TTLs are stored as seconds in an int, but expiry is computed using timeToLive * 1000 with int arithmetic. For TTL values greater than Integer.MAX_VALUE / 1000 (~2,147,483 seconds ≈ 24.85 days), the multiplication overflows and produces a negative millisecond offset. The resulting expires_when is in the past, so invites can appear immediately expired and any UI/API that interprets expiry will show incorrect results.
This is currently reachable from Qortal Hub, which offers a 30‑day invite TTL (2,592,000 seconds).
Steps to Reproduce
- In Qortal Hub → Groups → Invite Member, select “30 days” expiry (2,592,000 seconds).
- Invite any address.
- Query
/groups/invites/group/{groupId} or /groups/invites/{address} and inspect expires_when.
Expected
expires_when = inviteTimestamp + inviteTime * 1000
For 30 days, expiry should be ~30 days in the future.
Actual
expires_when is ~19.7 days in the past (because timeToLive * 1000 overflows int), so the stored expiry is wrong and can be interpreted as already expired.
Example math:
timeToLive = 2,592,000
timeToLive * 1000 (int) = 2,592,000,000 -> overflows to -1,702,967,296
expiry = timestamp + (-1,702,967,296)
Root Cause / Code References
src/main/java/org/qortal/group/Group.java
- Invite expiry:
expiry = groupInviteTransactionData.getTimestamp() + timeToLive * 1000;
- Ban expiry uses the same pattern:
expiry = groupBanTransactionData.getTimestamp() + timeToLive * 1000;
src/main/java/org/qortal/transaction/GroupInviteTransaction.java
- TTL validation only checks
< 0, so large TTLs are accepted.
src/main/java/org/qortal/data/transaction/GroupInviteTransactionData.java
User Impact
- Invites with TTL > ~24.85 days are stored as expired in the past.
- UI/API clients that display or filter by expiry will show invites as already expired.
- If expiry enforcement/cleanup is added later, these invites could be immediately removed/ignored.
- Same overflow pattern likely affects group bans with TTL (if/when used).
Suggested Fixes
- Use long arithmetic before multiplying to avoid overflow:
expiry = timestamp + (long) timeToLive * 1000L;
- Apply to both invite and ban expiry in
Group.java.
- Optionally validate upper bounds in
GroupInviteTransaction.isValid() (and GroupBan if TTL is used) to prevent unrealistic TTLs, or clamp to a safe max.
- Add regression tests:
- TTL = 2,592,000 should produce expiry in the future.
- TTL = 0 should result in
null expiry (no expiration).
Notes
- The default TTL used in Qortal Hub is 3 days (259,200 seconds), which is safe, but Hub also offers 30 days (2,592,000 seconds), which triggers the overflow.
- Simply switching to long arithmetic removes the overflow without changing on-chain formats.
Summary
Group invite TTLs are stored as seconds in an
int, but expiry is computed usingtimeToLive * 1000with int arithmetic. For TTL values greater thanInteger.MAX_VALUE / 1000(~2,147,483 seconds ≈ 24.85 days), the multiplication overflows and produces a negative millisecond offset. The resultingexpires_whenis in the past, so invites can appear immediately expired and any UI/API that interprets expiry will show incorrect results.This is currently reachable from Qortal Hub, which offers a 30‑day invite TTL (2,592,000 seconds).
Steps to Reproduce
/groups/invites/group/{groupId}or/groups/invites/{address}and inspectexpires_when.Expected
expires_when = inviteTimestamp + inviteTime * 1000For 30 days, expiry should be ~30 days in the future.
Actual
expires_whenis ~19.7 days in the past (becausetimeToLive * 1000overflowsint), so the stored expiry is wrong and can be interpreted as already expired.Example math:
Root Cause / Code References
src/main/java/org/qortal/group/Group.javaexpiry = groupInviteTransactionData.getTimestamp() + timeToLive * 1000;expiry = groupBanTransactionData.getTimestamp() + timeToLive * 1000;src/main/java/org/qortal/transaction/GroupInviteTransaction.java< 0, so large TTLs are accepted.src/main/java/org/qortal/data/transaction/GroupInviteTransactionData.javatimeToLiveisint.User Impact
Suggested Fixes
expiry = timestamp + (long) timeToLive * 1000L;Group.java.GroupInviteTransaction.isValid()(and GroupBan if TTL is used) to prevent unrealistic TTLs, or clamp to a safe max.nullexpiry (no expiration).Notes