aries-cloudagent-python icon indicating copy to clipboard operation
aries-cloudagent-python copied to clipboard

:bug: When auto-storing a credential fails, the cred ex record remains in state: done

Open ff137 opened this issue 4 months ago • 1 comments

I'm encountering an issue where I can reliably cause auto-storing of credentials to fail. (Related to revocation registry refactoring.)

The failure is not the issue here, but rather how acapy handles the auto-storing failure.

There are some auto-store failures that are unrecoverable (e.g. "Invalid state: Issuer is sending incorrect data", an error message that propagates from the anoncreds-rs and anoncreds-clsignatures-rs packages). Retrying to store this credential will continue to fail. Scenarios such as this should have the cred-ex-record never enter a done state (since receiving a webhook event indicating done would make one believe the credential was stored), and I believe it should instead go to an abandoned state.

Then there are other scenarios where auto-storing failed due to an error with the DB. These cases should in fact be retryable. Currently, it would not be possible for a holder to manually call the /issue-credential-2.0/records/{cred_ex_id}/store for this record, because current logic for store_credential requires the record to be in state: credential-received.

So there are a few issues here:

  • an auto-store failure should not have the record transition to a "done" state, since that would usually indicate the credential is successfully stored in the holder's wallet.
  • an auto-store failure that is unrecoverable should transition to abandoned (my proposal).
  • an auto-store failure that is recoverable should:
    • perhaps automatically be retried, up to 3-5 times with some delay (to account for DB errors);
    • and/or, remain in state credential-received so that it can be manually stored at a later time.

Notes:

  • V20CredIssueHandler is the relevant handler that does the if context.settings.get("debug.auto_store_credential") check.
  • This handler first calls V20CredManager.receive_credential, which ultimately calls receive_credential for the relevant V20CredFormatHandler (be it anoncreds, indy, ld_proof, vc_di).
    • All of those format handlers (apart from the ld_proof one) have a receive_credential method that looks like this:
        async def receive_credential(
          self, cred_ex_record: V20CredExRecord, cred_issue_message: V20CredIssue
      ) -> None:
          """Receive anoncreds credential.
    
          Validation is done in the store credential step.
          """
    
    • That's it ... no actual code. The receive credential method leaves the validation to be done at the store step, meaning that errors are only caught once storage is attempted. That's how it was done for indy, and that's presumably how it was copy-pasted for anoncreds and vc_di.

If it's an unrecoverable error (e.g. "Issuer is sending incorrect data"), then the holder currently gets a "done" webhook and thinks it was stored successfully. Storage should not be attempted in the first place! Because the validation error could be caught at the receive_credential phase instead. That would make that any failures that do occur at the store step, should be recoverable ones, and can therefore be retried internally at least a few times before notifying issuer/holder of the error.

ff137 avatar Jul 30 '25 11:07 ff137

Sample of a cred-ex record that failed, but remains in state done:

{
  "results": [
    {
      "cred_ex_record": {
        "state": "done",
        "created_at": "2025-07-30T11:22:23.525604Z",
        "updated_at": "2025-07-30T11:22:28.990892Z",
        "trace": false,
        "cred_ex_id": "3a7033e4-fe7d-4b68-814d-fbd785d94822",
        "connection_id": "17ae4645-a8e3-477c-8713-cc49c89fced6",
        "thread_id": "0925af1e-26f2-4c96-87b2-20d786fbfd81",
        "initiator": "external",
        "role": "holder",
        "cred_offer": {
          "@type": "https://didcomm.org/issue-credential/2.0/offer-credential",
          "@id": "0925af1e-26f2-4c96-87b2-20d786fbfd81",
          "~thread": {},
          "comment": "create automated v2.0 credential exchange record",
          "credential_preview": {
            "@type": "https://didcomm.org/issue-credential/2.0/credential-preview",
            "attributes": [
              {
                "name": "speed",
                "value": "1"
              },
              {
                "name": "name",
                "value": "Alice"
              },
              {
                "name": "age",
                "value": "44"
              }
            ]
          },
          "formats": [
            {
              "attach_id": "anoncreds",
              "format": "anoncreds/[email protected]"
            }
          ],
          "offers~attach": [
            {
              "@id": "anoncreds",
              "mime-type": "application/json",
              "data": {
                "base64": ...
              }
            }
          ]
        },
        "cred_request": {
          "@type": "https://didcomm.org/issue-credential/2.0/request-credential",
          "@id": "6bfa8c02-6756-450d-8275-b9e65fecff37",
          "~thread": {
            "thid": "0925af1e-26f2-4c96-87b2-20d786fbfd81"
          },
          "formats": [
            {
              "attach_id": "anoncreds",
              "format": "anoncreds/[email protected]"
            }
          ],
          "requests~attach": [
            {
              "@id": "anoncreds",
              "mime-type": "application/json",
              "data": {
                "base64": ...
              }
            }
          ]
        },
        "cred_issue": {
          "@type": "https://didcomm.org/issue-credential/2.0/issue-credential",
          "@id": "89237a2e-f6db-48f8-9d9f-aeb79705b81e",
          "~thread": {
            "thid": "0925af1e-26f2-4c96-87b2-20d786fbfd81"
          },
          "formats": [
            {
              "attach_id": "anoncreds",
              "format": "anoncreds/[email protected]"
            }
          ],
          "credentials~attach": [
            {
              "@id": "anoncreds",
              "mime-type": "application/json",
              "data": {
                "base64": ...
              }
            }
          ]
        },
        "by_format": {
          "cred_offer": {
            "anoncreds": {
              "schema_id": "did:cheqd:testnet:96f6c7f6-771f-40c2-b09d-473e59f93ef1/resources/dbf765e9-f4db-4d92-8b0e-4439b6d7df0c",
              "cred_def_id": "did:cheqd:testnet:7f4aadfe-2f8f-4d13-920c-dacb81b6556c/resources/2d88c5d1-defe-40e6-ae8b-8fd4eeec44ea",
              "key_correctness_proof": {
                "c": "21642334993430075873773874859085230091256529842874326286924907205798543408583",
                "xz_cap": ...
                "xr_cap": [
                  [
                    "speed",
                    ...
                  ],
                  [
                    "master_secret",
                    ...
                  ],
                  [
                    "age",
                    ...
                  ],
                  [
                    "name",
                    ...
                  ]
                ]
              },
              "nonce": "324254108575112728225782"
            }
          },
          "cred_request": {
            "anoncreds": {
              "entropy": "did:peer:4zQmVprF8fqAdzir6KWNYD57hm5qVVxBeyRxdNgDABqBRjHp",
              "cred_def_id": "did:cheqd:testnet:7f4aadfe-2f8f-4d13-920c-dacb81b6556c/resources/2d88c5d1-defe-40e6-ae8b-8fd4eeec44ea",
              "blinded_ms": {
                "u": ...
                "ur": ...
                "hidden_attributes": [
                  "master_secret"
                ],
                "committed_attributes": {}
              },
              "blinded_ms_correctness_proof": {
                "c": "104635856726070762056781345808499843581358802327906120712378905260912385972484",
                "v_dash_cap": ...,
                "m_caps": {
                  "master_secret": ...
                },
                "r_caps": {}
              },
              "nonce": "596969740634215848576564"
            }
          },
          "cred_issue": {
            "anoncreds": {
              "schema_id": "did:cheqd:testnet:96f6c7f6-771f-40c2-b09d-473e59f93ef1/resources/dbf765e9-f4db-4d92-8b0e-4439b6d7df0c",
              "cred_def_id": "did:cheqd:testnet:7f4aadfe-2f8f-4d13-920c-dacb81b6556c/resources/2d88c5d1-defe-40e6-ae8b-8fd4eeec44ea",
              "rev_reg_id": "did:cheqd:testnet:7f4aadfe-2f8f-4d13-920c-dacb81b6556c/resources/5d7e1df0-4887-4178-a609-dd5c29492def",
              "values": {
                "name": {
                  "raw": "Alice",
                  "encoded": "27034640024117331033063128044004318218486816931520886405535659934417438781507"
                },
                "speed": {
                  "raw": "1",
                  "encoded": "1"
                },
                "age": {
                  "raw": "44",
                  "encoded": "44"
                }
              },
              "signature": {
                "p_credential": {
                  "m_2": ...,
                  "a": ...,
                  "e": ...,
                  "v": ...
                },
                ...
              },...
            }
          }
        },
        "auto_offer": false,
        "auto_issue": false,
        "auto_remove": false,
        "error_msg": "Error processing received credential. Invalid state: Issuer is sending incorrect data."
      },
      "anoncreds": {
        "created_at": "2025-07-30T11:22:23.994774Z",
        "updated_at": "2025-07-30T11:22:23.994774Z",
        "cred_ex_anoncreds_id": "50c44a0a-92be-412b-ab1a-5fb26cff300a",
        "cred_ex_id": "3a7033e4-fe7d-4b68-814d-fbd785d94822",
        "cred_request_metadata": {
          "link_secret_blinding_data": {
            "v_prime": ...,
            "vr_prime": "214695FA1618C17874D4688EA6B3A9249D3FDEE1D6A5E30A854A93D0D36177B9"
          },
          "nonce": "596969740634215848576564",
          "link_secret_name": "default"
        }
      },
      "indy": null,
      "ld_proof": null,
      "vc_di": null
    }
  ]
}

Note: error_msg is present, but record is in state: done. I would expect an error to mean it should be abandoned.

ff137 avatar Jul 30 '25 12:07 ff137