Skip to content

Commit

Permalink
Merge pull request #89 from ianco/master
Browse files Browse the repository at this point in the history
Add support for V20 credential exchange
  • Loading branch information
esune authored Jun 6, 2024
2 parents be61ffa + 90555c8 commit 4ef9298
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 22 deletions.
1 change: 1 addition & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ services:
environment:
- WEBHOOK_PORT=${WEBHOOK_PORT}
- VONX_API_URL=${VONX_API_URL:-http://myorg-controller:8000}
- ISSUE_CRED_VERSION=${ISSUE_CRED_VERSION:-V20}
# [pipeline data source (directory)]
#- EAO_MDB_DB_HOST=${EAO_MDB_DB_HOST:-mongo}
#- EAO_MDB_DB_PORT=${EAO_MDB_DB_PORT:-27017}
Expand Down
1 change: 1 addition & 0 deletions docker/manage
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ configureEnvironment () {
export APPLICATION_URL=${APPLICATION_URL-http://localhost:${WEB_HTTP_PORT:-5000}}
export ENDPOINT_URL=http://${ENDPOINT_HOST-$DOCKERHOST:${WEB_HTTP_PORT:-5001}}
export VONX_API_URL=http://myorg-controller:${WEBHOOK_PORT}
export ISSUE_CRED_VERSION=${ISSUE_CRED_VERSION:-"V20"}

# myorg-controller
# specify this as anything other than "true" to force manual connection to the TOB agent
Expand Down
40 changes: 40 additions & 0 deletions issuer_controller/src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,32 @@ def submit_credential():
return response


@app.route('/issue-credential-v20', methods=['POST'])
def submit_credential_v20():
"""
Exposed method to proxy credential issuance requests.
"""
if not issuer.tob_connection_synced():
abort(503, "Connection not yet synced")

start_time = time.perf_counter()
method = 'submit_credential_v20.batch'

if not request.json:
end_time = time.perf_counter()
issuer.log_timing_method(method, start_time, end_time, False)
abort(400)

cred_input = request.json

response = issuer.handle_send_credential_v20(cred_input)

end_time = time.perf_counter()
issuer.log_timing_method(method, start_time, end_time, True)

return response


@app.route("/api/agentcb/topic/<topic>/", methods=["POST"])
@authentication.api_key_required
def agent_callback(topic):
Expand Down Expand Up @@ -141,13 +167,27 @@ def agent_callback(topic):
else:
response = jsonify({})

elif topic == issuer.TOPIC_CREDENTIALS_V20 or topic == issuer.TOPIC_CREDENTIALS_V20_INDY:
if "state" in message:
method = method + '.' + message["state"]
response = issuer.handle_credentials_v20(message["state"], message)
else:
response = jsonify({})

elif topic == issuer.TOPIC_PRESENTATIONS:
if "state" in message:
method = method + "." + message["state"]
response = issuer.handle_presentations(message["state"], message)
else:
response = jsonify({})

elif topic == issuer.TOPIC_PRESENTATIONS_V20:
if "state" in message:
method = method + '.' + message["state"]
response = issuer.handle_presentations_v20(message["state"], message)
else:
response = jsonify({})

elif topic == issuer.TOPIC_GET_ACTIVE_MENU:
response = issuer.handle_get_active_menu(message)

Expand Down
153 changes: 146 additions & 7 deletions issuer_controller/src/issuer.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,10 @@ def get_credential_response(cred_exch_id):
TOPIC_CONNECTIONS = "connections"
TOPIC_CONNECTIONS_ACTIVITY = "connections_actvity"
TOPIC_CREDENTIALS = "issue_credential"
TOPIC_CREDENTIALS_V20 = "issue_credential_v2_0"
TOPIC_CREDENTIALS_V20_INDY = "issue_credential_v2_0_indy"
TOPIC_PRESENTATIONS = "handle_present_proof"
TOPIC_PRESENTATIONS_V20 = "present_proof_v2_0"
TOPIC_PRESENTATIONS = "presentations"
TOPIC_GET_ACTIVE_MENU = "get-active-menu"
TOPIC_PERFORM_MENU_ACTION = "perform-menu-action"
Expand Down Expand Up @@ -694,7 +698,7 @@ def handle_credentials(state, message):
)
else:
pass
if state == "credential_acked":
if state == "credential_acked" or state == "done":
# raise 10% errors
do_error = random.randint(1, 100)
if do_error <= ACK_ERROR_PCT:
Expand All @@ -711,11 +715,42 @@ def handle_credentials(state, message):
return jsonify({"message": state})


def handle_credentials_v20(state, message):
start_time = time.perf_counter()
method = "Handle callback:" + state
log_timing_event(method, message, start_time, None, False)

if "thread_id" in message:
set_credential_thread_id(
message["cred_ex_id"], message["thread_id"]
)
else:
pass
if state == "credential_acked" or state == "done":
# raise 10% errors
#do_error = random.randint(1, 100)
#if do_error <= 10:
# raise Exception("Fake exception to test error handling: " + message["thread_id"])
response = {"success": True, "result": message["cred_ex_id"]}
add_credential_response(message["cred_ex_id"], response)

end_time = time.perf_counter()
processing_time = end_time - start_time
log_timing_event(method, message, start_time, end_time, True, outcome=str(state))

return jsonify({"message": "state"})


def handle_presentations(state, message):
# TODO auto-respond to proof requests
return jsonify({"message": state})


def handle_presentations_v20(state, message):
# TODO auto-respond to proof requests
return jsonify({"message": state})


def handle_get_active_menu(message):
# TODO add/update issuer info?
return jsonify({})
Expand Down Expand Up @@ -763,10 +798,17 @@ def run(self):
)
response.raise_for_status()
cred_data = response.json()
credential_exchange_id = None
if "credential_exchange_id" in cred_data:
credential_exchange_id = cred_data["credential_exchange_id"]
result_available = add_credential_request(
cred_data["credential_exchange_id"]
)
elif "cred_ex_id" in cred_data:
credential_exchange_id = cred_data["cred_ex_id"]
result_available = add_credential_request(
cred_data["cred_ex_id"]
)
else:
raise Exception(json.dumps(cred_data))

Expand All @@ -775,12 +817,12 @@ def run(self):
MAX_CRED_RESPONSE_TIMEOUT
):
add_credential_timeout_report(
cred_data["credential_exchange_id"], cred_data["thread_id"]
credential_exchange_id, cred_data["thread_id"]
)
LOGGER.error(
"Got credential TIMEOUT: %s %s %s",
cred_data["thread_id"],
cred_data["credential_exchange_id"],
credential_exchange_id,
cred_data["connection_id"],
)
end_time = time.perf_counter()
Expand All @@ -791,7 +833,7 @@ def run(self):
False,
data={
"thread_id": cred_data["thread_id"],
"credential_exchange_id": cred_data["credential_exchange_id"],
"credential_exchange_id": credential_exchange_id,
"Error": "Timeout",
"elapsed_time": (end_time - start_time),
},
Expand All @@ -807,7 +849,7 @@ def run(self):

# there should be some form of response available
self.cred_response = get_credential_response(
cred_data["credential_exchange_id"]
credential_exchange_id
)

except Exception as exc:
Expand All @@ -818,11 +860,11 @@ def run(self):
outcome = str(exc)
if cred_data:
add_credential_exception_report(
cred_data["credential_exchange_id"], exc
credential_exchange_id, exc
)
data = {
"thread_id": cred_data["thread_id"],
"credential_exchange_id": cred_data["credential_exchange_id"],
"credential_exchange_id": credential_exchange_id,
"Error": str(exc),
"elapsed_time": (end_time - start_time),
}
Expand Down Expand Up @@ -935,3 +977,100 @@ def handle_send_credential(cred_input):
print(" ", processing_time / processed_count, "seconds per credential")

return jsonify(cred_responses)


def handle_send_credential_v20(cred_input):
"""
# other sample data
sample_credentials = [
{
"schema": "ian-registration.ian-ville",
"version": "1.0.0",
"attributes": {
"corp_num": "ABC12345",
"registration_date": "2018-01-01",
"entity_name": "Ima Permit",
"entity_name_effective": "2018-01-01",
"entity_status": "ACT",
"entity_status_effective": "2019-01-01",
"entity_type": "ABC",
"registered_jurisdiction": "BC",
"effective_date": "2019-01-01",
"expiry_date": ""
}
},
{
"schema": "ian-permit.ian-ville",
"version": "1.0.0",
"attributes": {
"permit_id": str(uuid.uuid4()),
"entity_name": "Ima Permit",
"corp_num": "ABC12345",
"permit_issued_date": "2018-01-01",
"permit_type": "ABC",
"permit_status": "OK",
"effective_date": "2019-01-01"
}
}
]
"""
# construct and send the credential
global app_config

agent_admin_url = app_config["AGENT_ADMIN_URL"]

start_time = time.perf_counter()
processing_time = 0
processed_count = 0

# let's send a credential!
cred_responses = []
for credential in cred_input:
cred_def_key = "CRED_DEF_" + credential["schema"] + "_" + credential["version"]
credential_definition_id = app_config["schemas"][cred_def_key]

credential_attributes = []
for attribute in credential["attributes"]:
credential_attributes.append({
"name": attribute,
"value": credential["attributes"][attribute]
})
cred_offer = {
"credential_preview": {
"@type": "issue-credential/2.0/credential-preview",
"attributes": credential_attributes
},
"filter": {
"indy": {
"schema_id": app_config["schemas"][
"SCHEMA_" + credential["schema"] + "_" + credential["version"]
],
"schema_name": credential["schema"],
"issuer_did": app_config["DID"],
"schema_version": credential["version"],
"schema_issuer_did": app_config["DID"],
"cred_def_id": credential_definition_id,
}
},
"comment": "",
"connection_id": app_config["TOB_CONNECTION"],
}
do_trace = random.randint(1, 100)
if do_trace <= TRACE_MSG_PCT:
cred_offer["trace"] = True
thread = SendCredentialThread(
credential_definition_id,
cred_offer,
agent_admin_url + "/issue-credential-2.0/send",
ADMIN_REQUEST_HEADERS,
)
thread.start()
thread.join()
cred_responses.append(thread.cred_response)
processed_count = processed_count + 1

processing_time = time.perf_counter() - start_time
print(">>> Processed", processed_count, "credentials in", processing_time)
print(" ", processing_time/processed_count, "seconds per credential")

return jsonify(cred_responses)
27 changes: 12 additions & 15 deletions issuer_pipeline/von_pipeline/credssubmitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@

AGENT_URL = os.environ.get('VONX_API_URL', 'http://localhost:5000')

ISSUE_CRED_VERSION = os.getenv('ISSUE_CRED_VERSION', 'V10')
if not ISSUE_CRED_VERSION in ['V10', 'V20']:
raise Exception(f"Unsupported Issue Credential version: {ISSUE_CRED_VERSION}")

CREDS_BATCH_SIZE = 3000
CREDS_REQUEST_SIZE = 5 # use 1 because it's more likely to trigger deadlocks
MAX_CREDS_REQUESTS = 16
Expand All @@ -48,25 +52,22 @@ async def submit_cred_batch(http_client, creds):
'Credentials could not be processed: {}'.format(await response.text())
)
result_json = await response.json()
#print('Response from von-x:\n{}\n'.format(result_json))
return result_json
except Exception as exc:
print(exc)
raise

async def submit_cred(http_client, attrs, schema, version):
async def submit_cred_batch_v20(http_client, creds):
try:
response = await http_client.post(
'{}/issue-credential'.format(AGENT_URL),
params={'schema': schema, 'version': version},
json=attrs
'{}/issue-credential-v20'.format(AGENT_URL),
json=creds
)
if response.status != 200:
raise RuntimeError(
'Credential could not be processed: {}'.format(await response.text())
'Credentials could not be processed: {}'.format(await response.text())
)
result_json = await response.json()
#print('Response from von-x:\n{}\n'.format(result_json))
return result_json
except Exception as exc:
print(exc)
Expand All @@ -93,14 +94,10 @@ async def post_credentials(http_client, conn, credentials):
cur2 = None
results = None
try:
#print('=============')
#print(post_creds)
#print('=============')
# old code for submitting one credential at a time
# result_json = await submit_cred(http_client, credential['CREDENTIAL_JSON'], credential['SCHEMA_NAME'], credential['SCHEMA_VERSION'])
results = await submit_cred_batch(http_client, post_creds)

#print("Posted = ", len(credentials), ", results = ", len(results))
if ISSUE_CRED_VERSION == "V20":
results = await submit_cred_batch_v20(http_client,post_creds)
else:
results = await submit_cred_batch(http_client, post_creds)

for i in range(len(credentials)):
credential = credentials[i]
Expand Down

0 comments on commit 4ef9298

Please sign in to comment.