fix: self-issuance/self-connection OOB and DID exchange failures - #4174
fix: self-issuance/self-connection OOB and DID exchange failures#4174nb-vivek-bodar wants to merge 6 commits into
Conversation
Fixes two related failures reported in openwallet-foundation#3300 when an agent creates an out-of-band invitation and receives it back into its own wallet (self-issuance / self-connection): - oob_processor.find_oob_record_for_inbound_message raised StorageDuplicateError because two OobRecords (role=sender and role=receiver) share the same invi_msg_id in a self-connection. Disambiguate using the inbound message type: messages a receiver always sends back to a sender (didexchange request, handshake-reuse) belong to the role=sender record; other messages (e.g. attached protocol messages like credential offers) belong to the role=receiver record. - DIDXManager.accept_complete raised DIDXManagerError ("No corresponding connection request found") because the OOB "connection reuse" cleanup in find_oob_record_for_inbound_message deleted the wallet's own other-role ConnRecord, mistaking it for a stale connection superseded by a legitimate RFC 0434 connection reuse. In a self-connection, that "old" ConnRecord is still awaiting its own DIDXComplete. Detect this case by checking for reciprocal DIDs between the "old" and current connection records (unique to a self-connection) and skip the deletion when it applies, leaving the genuine stale-connection-reuse cleanup path unchanged. Also removes two redundant ConnRecord.state saves (in DIDXManager.receive_invitation and DIDXRequestHandler.handle) that ran after sending a message whose delivery can, for a self-connection, synchronously advance the same ConnRecord further before the redundant save executes, regressing it back to an earlier state. Added regression tests parametrized across askar and askar-anoncreds wallet backends covering: self-issuance via an OOB attachment without a prior connection, a full connectionless self-issuance round trip, a self-connection did-exchange handshake reaching completion for both of the wallet's own ConnRecords, and confirmation that normal two-party OOB/did-exchange flows are unaffected. Signed-off-by: Vivek <vivek@northernblock.io>
Companion doc for the self-issuance/self-connection fix, detailing the two root causes from openwallet-foundation#3300 and how each was resolved. Signed-off-by: Vivek <vivek@northernblock.io>
…d-not-found In a self-connection created from an OOB invitation carrying both a handshake protocol and an attached credential offer (issue openwallet-foundation#3300), the holder's request-credential was answered with a problem report (record-not-found) and both exchange records ended up abandoned: - After the handshake, the receiver-role OobRecord is deleted (normal cleanup), so the inbound attached offer's pthid lookup matched the wallet's own role=sender OobRecord and consumed (deleted) it, even though an offer always flows sender -> receiver and can never belong to the sender's own record. Add a receiver-bound message-type guard in find_oob_record_for_inbound_message so attached offer/presentation request messages never consume a role=sender OobRecord. - With no OobRecord left when the request-credential arrived, V20CredManager.receive_request filtered by the inbound connection id, which can never match an issuer exchange record created as an OOB attachment (connection_id=None). Fall back to a thread/role-only lookup and adopt the connection on the record. Either change alone unblocks the flow; together they are defense-in-depth. Regression tests added for both. Signed-off-by: Vivek <vivek@northernblock.io>
For OOB attached messages the inbound message carries a connection_id, and Dispatcher.handle_v1_message resolved that ConnRecord using the dispatcher's own root profile instead of the profile the message was dispatched with. In a multitenant agent the connection lives in the recipient subwallet, so the lookup raised StorageNotFoundError and the attached message (e.g. a credential offer) was never handled. Use the profile passed in with the message, consistent with the find_inbound_connection branch directly below. In a single-wallet agent both profiles are the same object, so behavior there is unchanged. Signed-off-by: Vivek <vivek@northernblock.io>
swcurran
left a comment
There was a problem hiding this comment.
I don't like the "...OOBFix.md" documentation as a permanent artifact. Documentation of bugs that are fixed go into the PR, and the CHANGELOG when we release. If it is useful to document "Self-Connection" as a feature (which we probably shouldn't), that might be reasonable, but not how something that should work was broken and then fixed.
Suggest removing that, and either not including anything, or finding a place in the existing documentation to say "self-connection is supported" and any details an implementer needs to know.
|
Bug-fix narratives belong in the PR description and release CHANGELOG, not as a permanent docs page. Signed-off-by: Vivek <vivek@northernblock.io>
|
Removed the doc file. That's already covered in the PR description, so I don't think we need a separate page for it. |



Description
Fixes self-issuance / self-connection failures when an agent creates an out-of-band invitation and receives it back into its own wallet:
StorageDuplicateErrorinfind_oob_record_for_inbound_message: a self-connection produces two OobRecords (role=sender and role=receiver) sharing oneinvi_msg_id. Now disambiguated by the recipient verkey the message was delivered to, with a message-direction heuristic as fallback (didexchange request / handshake-reuse always belong to the role=sender record).DIDXManagerError("No corresponding connection request found") inaccept_complete: the OOB connection-reuse cleanup deleted the wallet's own mirror ConnRecord (reciprocal DIDs) while it was still awaiting its own DIDXComplete. The reciprocal-DID case is now detected and skipped; genuine stale-connection-reuse cleanup is unchanged.ConnRecord state regression in self-connections: removed two redundant post-send
ConnRecord.statesaves that could overwrite a state the (synchronous, self-addressed) delivery had already advanced.record-not-foundproblem report for the holder'srequest-credential(invitation with both a handshake protocol and an attached offer): after the handshake, the receiver-role OobRecord is deleted, so the inbound attached offer's pthid lookup matched and consumed the wallet's own role=sender OobRecord. A receiver-bound message-type guard now prevents attached offer / presentation-request messages from ever consuming a role=sender OobRecord. Additionally,V20CredManager.receive_requestfalls back to a thread/role-only lookup when the connection-filtered lookup misses (the issuer record was created connectionless), while rejecting records already bound to a different connection.Multitenant:
StorageNotFoundErrordispatching an OOB attached message (Record not found: connection/...): for OOB attached messages the inbound message carries aconnection_id, andDispatcher.handle_v1_messageresolved that ConnRecord using the dispatcher's own root profile instead of the profile the message was dispatched with. In a multitenant agent the connection lives in the recipient subwallet, so the attached message (e.g. the credential offer) was never handled whenever a connection was associated with it (existing-connection reuse, or handshake completed before attachment processing). The lookup now uses the message's profile, consistent with thefind_inbound_connectionbranch directly below; in a single-wallet agent both profiles are the same object, so behavior there is unchanged.Normal two-party flows are unaffected: the OOB guard only fires when an inbound offer / presentation request resolves to the wallet's own role=sender record (impossible in a two-party exchange), and the
receive_requestfallback only runs where the previous code already failed withStorageNotFoundError.Regression tests are included for every failure point, parametrized across askar and askar-anoncreds, including guards that normal two-party OOB / did-exchange flows still behave as before. Verified manually end-to-end in a multitenant deployment (invitation with handshake + attached offer received into the same wallet, through to the credential stored and both exchange records reaching
done).Related Issue
Closes #3300
Type of Change
Checklist