You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fixes#285 - Bug: GROUP_INVITE expiration not enforced (invites never expire)
Summary
Add the groupInviteExpiryHeight feature trigger (enum entry + getter) and wire it through mainnet, testnet, and all test-chain fixtures; startup now validates the trigger presence.
Implement invite expiry enforcement in Group.join(...) gated by the trigger: invite-first joins use join tx timestamps with an inclusive boundary and expiry == null sentinel; expired invites are treated as absent and fall back to join requests; pre-trigger behavior is unchanged. Join-first auto-approvals remain TTL-agnostic (any invite approves a pending request).
Apply chain-tip-based invite filtering to API endpoints via a shared helper in GroupsResource (inclusive boundary, expiry == null never expires, skip when no tip) and document the behavior in swagger.
Update docs (INVITE_EXPIRATION.md, IMPLEMENTATION.md, CONSENSUS_CHANGE.md) with final semantics, activation plan, join-first clarifications, and activation rollout guidance.
Add comprehensive tests: invite-first expiry enforcement, join-first TTL-agnostic behavior, backdated/forward-dated windows, pre/post-trigger activation, and API invite filtering (chain-tip filtering and no-tip fallback). Includes a dedicated GroupInviteFilteringApiTests class.
Tests
mvn -q -DskipTests=false -Dtest=org.qortal.test.group.MiscTests test
mvn -q -DskipTests=false -Dtest=org.qortal.test.api.GroupInviteFilteringApiTests test
Follow-up for activation
Choose and set a real groupInviteExpiryHeight for mainnet once network coverage is sufficient; keep the placeholder until then.
Communicate the activation height/date to operators and include it in release notes/changelog.
Verify testnet/fixture triggers remain at immediate activation for ongoing coverage, adjusting only if the activation scheme changes.
Docs of this PR should be gathered in a dedicated folder ("group_invite_expiry" for example).
If the docs folder grows up with more documents, it's very difficult to understand to what feature the single document refers to.
Docs of this PR should be gathered in a dedicated folder ("group_invite_expiry" for example). If the docs folder grows up with more documents, it's very difficult to understand to what feature the single document refers to.
All the documents created were only for the purpose of working on the implementation of this one single feature. They are meant to be temporary can be discarded later if these changes get approved and merged in. They provide a clear explanation of the issue and the causes, as well as other considerations that had to be made, and a record of the which decisions were chosen and why, and also a record of the work that was done. They can be moved to another folder, deleted, or left as is. That doesn't matter because .md files don't affect chain consensus.
The two responses I have gotten about this so far were both asking about the documents. That's not important at all. The code needs to be checked, reviewed, and merged so we can finally get this fixed. The docs can be deleted later. They were just meant to help the reviewers. Every file, every commit, every desicion - all explained, in case there were any questions about why anything was done. @kennycud@IceBurst
What: Non-expiring invite (TTL=0) still approves a stored join request.
How: Create request, mint TTL=0 invite, assert membership and cleanup.
Why: Ensures TTL=0 sentinel applies in join-first auto-approval.
Output:
[testJoinFirstInviteLaterTtlZero] START
Stored join request? true
Membership after TTL=0 invite? true
PASS
testApiFiltersExpiredInvites
What: API endpoints omit expired invites and return non-expiring ones using chain-tip time.
How: Mint an expired invite and a TTL=0 invite, call both invite endpoints, assert expired invite hidden and TTL=0 visible.
Why: Confirms chain-tip-based filtering behavior exposed via API.
Output:
[testApiFiltersExpiredInvites] START
Minting expired invite at 1764917670601 for bob
Minting TTL=0 invite for chloe
Group invites returned: 1
Invites for Chloe: 1
Invites for Bob (expired should be filtered): 0
PASS
testPrePostTriggerActivation
What: Expired invites auto-add pre-trigger but become requests post-trigger.
How: Use reflection to raise the trigger (pre) to allow expired invite membership, then restore trigger and assert expired invite becomes a request post-trigger.
Why: Validates trigger-gated activation of invite expiry enforcement.
Output:
[testPrePostTriggerActivation] START
Pre-trigger join timestamp 1764917676864 relative to expiry 1764917675864
Pre-trigger membership? true
Post-trigger join timestamp 1764917676945 relative to expiry 1764917675945
Post-trigger membership? false
Post-trigger request stored and invite retained
PASS
testInviteFilteringByChainTip
What: API invite filtering hides expired invites and retains TTL=0/unexpired invites using chain-tip timestamp.
How: Mint an expired invite, a non-expiring invite, and an unexpired invite; call invitee and group endpoints; assert expired invite filtered out and others returned.
Why: Verifies chain-tip-based filtering behavior exposed via API.
Output:
TEST START: testInviteFilteringByChainTip - expired invites filtered, TTL=0/unexpired retained.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #285 - Bug: GROUP_INVITE expiration not enforced (invites never expire)
Summary
groupInviteExpiryHeightfeature trigger (enum entry + getter) and wire it through mainnet, testnet, and all test-chain fixtures; startup now validates the trigger presence.Group.join(...)gated by the trigger: invite-first joins use join tx timestamps with an inclusive boundary andexpiry == nullsentinel; expired invites are treated as absent and fall back to join requests; pre-trigger behavior is unchanged. Join-first auto-approvals remain TTL-agnostic (any invite approves a pending request).GroupsResource(inclusive boundary,expiry == nullnever expires, skip when no tip) and document the behavior in swagger.INVITE_EXPIRATION.md,IMPLEMENTATION.md,CONSENSUS_CHANGE.md) with final semantics, activation plan, join-first clarifications, and activation rollout guidance.GroupInviteFilteringApiTestsclass.Tests
mvn -q -DskipTests=false -Dtest=org.qortal.test.group.MiscTests testmvn -q -DskipTests=false -Dtest=org.qortal.test.api.GroupInviteFilteringApiTests testFollow-up for activation
groupInviteExpiryHeightfor mainnet once network coverage is sufficient; keep the placeholder until then.