Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Concordium account key files
*.export

target
dist
.eslintcache
Expand Down
77 changes: 77 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions credential-verification-service/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Unreleased

- Added account nonce management to the service.
- Added the logic of the `/verifiable-presentations/create-verification-request` api endpoint flow. This endpoint submits the `verification-request-anchor (VRA)` on-chain.
- Initial service.
5 changes: 5 additions & 0 deletions credential-verification-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ tokio-util.workspace = true
prometheus-client.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
tonic = {workspace = true, features = ["tls", "tls-roots"]}
thiserror.workspace = true
tower-http = {workspace = true, features = [
"compression-zstd",
] }

[[bin]]
name = "credential-verification-service"
Expand Down
32 changes: 30 additions & 2 deletions credential-verification-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,35 @@ docker run --rm \
credential-verification-service
```


you should then be able to curl the health endpoint from outside the container, for example:
You should then be able to curl the health endpoint from outside the container, for example:

`curl http://localhost:8001/health`

## Build the service from the source code

You can build the serive locally as follows:

```
cargo build
```

## Run the servie from the source code

You can run the serive locally as follows:

```
cargo run -- --node-endpoint https://grpc.testnet.concordium.com:20000 --account 4bbdAUCDK2D6cUvUeprGr4FaSaHXKuYmYVjyCa4bXSCu3NUXzA.export
```

## Configuration options

The following options are supported:

- `--node-endpoint [env: CREDENTIAL_VERIFICATION_SERVICE_NODE_GRPC_ENDPOINT]`: the URL of the node's GRPC V2 interface, e.g., http://node.testnet.concordium.com:20000
- `--request-timeout [env: CREDENTIAL_VERIFICATION_SERVICE_REQUEST_TIMEOUT]`: The request timeout (both of request to the node and server requests) in milliseconds.
- `--log-level [env: CREDENTIAL_VERIFICATION_SERVICE_LOG_LEVEL]`: The maximum log level (defaults to info if not given).
- `--account [env: CREDENTIAL_VERIFICATION_SERVICE_ACCOUNT]`: The path to the account key file.
- `--api-address [env: CREDENTIAL_VERIFICATION_SERVICE_API_ADDRESS]`: The socket address where the service exposes its API (defaults to `127.0.0.1:8000` if not given).
- `--monitoring-address [env: CREDENTIAL_VERIFICATION_SERVICE_MONITORING_ADDRESS]`: The socket address used for health and metrics monitoring (defaults to `127.0.0.1:8001` if not given).
- `--transaction-expiry [env: CREDENTIAL_VERIFICATION_SERVICE_TRANSACTION_EXPIRY]`: The number of seconds in the future when the anchor transactions should expiry (defaults to 1000000 if not given).

22 changes: 17 additions & 5 deletions credential-verification-service/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
use axum::{Router, routing::get};
use crate::types::Service;
use axum::{
Router,
routing::{get, post},
};
use prometheus_client::registry::Registry;
use std::sync::Arc;

use crate::service::Service;

mod monitoring;
mod verification_request;
mod verifier;

/// Router exposing the service's endpoints
pub fn router(service: Arc<Service>) -> Router {
pub fn router(service: Arc<Service>, request_timeout: u64) -> Router {
Router::new()
.route("/verify", get(verifier::verify))
.route("/verifiable-presentations/verify", post(verifier::verify))
.route(
"/verifiable-presentations/create-verification-request",
post(verification_request::create_verification_request),
)
.with_state(service)
.layer(tower_http::timeout::TimeoutLayer::new(
std::time::Duration::from_millis(request_timeout),
))
.layer(tower_http::limit::RequestBodyLimitLayer::new(1_000_000)) // at most 1000kB of data.
.layer(tower_http::compression::CompressionLayer::new())
}

/// Router exposing the Prometheus metrics and health endpoint.
Expand Down
98 changes: 98 additions & 0 deletions credential-verification-service/src/api/verification_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! Handler for create-verification-request endpoint.
use crate::types::{ServerError, Service, VerificationRequestParams};
use axum::{Json, extract::State};
use concordium_rust_sdk::{
base::web3id::v1::anchor::VerificationRequest,
v2::{QueryError, RPCError},
web3id::v1::CreateAnchorError::Query,
web3id::v1::create_verification_request_and_submit_request_anchor,
{
base::web3id::v1::anchor::{
IdentityProviderDid, RequestedIdentitySubjectClaimsBuilder,
UnfilledContextInformationBuilder, VerificationRequestDataBuilder,
},
common::types::TransactionTime,
web3id::v1::AnchorTransactionMetadata,
},
};
use std::sync::Arc;

pub async fn create_verification_request(
State(state): State<Arc<Service>>,
Json(params): Json<VerificationRequestParams>,
) -> Result<Json<VerificationRequest>, ServerError> {
let statement = RequestedIdentitySubjectClaimsBuilder::default()
.issuers(
params
.issuers
.iter()
.map(|issuer| IdentityProviderDid::new(*issuer, state.network)),
)
.statements(params.statements)
.sources(params.credential_types)
.build();

let context = UnfilledContextInformationBuilder::new_simple(
params.nonce,
params.connection_id,
params.context_string,
)
.build();

let verification_request_data = VerificationRequestDataBuilder::new(context)
.subject_claim(statement)
.build();

// Transaction should expiry after some seconds.
let expiry = TransactionTime::seconds_after(state.transaction_expiry_secs);

let mut node_client = state.node_client.clone();

// Get the current nonce for the backend wallet and lock it. This is necessary
// since it is possible that API requests come in parallel. The nonce is
// increased by 1 and its lock is released after the transaction is submitted to
// the blockchain.
let mut account_sequence_number = state.nonce.lock().await;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a timeout on this async operation (locking), like we have for calling the node.


let anchor_transaction_metadata = AnchorTransactionMetadata {
signer: &state.account_keys,
sender: state.account_keys.address,
account_sequence_number: *account_sequence_number,
expiry,
};

let verification_request = create_verification_request_and_submit_request_anchor(
&mut node_client,
anchor_transaction_metadata,
verification_request_data,
Some(params.public_info),
)
.await;

match verification_request {
Ok(verification_request) => {
// If the submission of the anchor transaction was successful,
// increase the account_sequence_number tracked in this service.
*account_sequence_number = account_sequence_number.next();

Ok(Json(verification_request))
}

Err(e) => {
// If the error is due to an account sequence number mismatch,
// refresh the value in the state.
if let Query(QueryError::RPCError(RPCError::CallError(ref err))) = e {
if err.message() == "Duplicate nonce" || err.message() == "Nonce too large" {
let nonce_response = node_client
.get_next_account_sequence_number(&state.account_keys.address)
.await
.map_err(|e| ServerError::SubmitAnchorTransaction(e.into()))?;
*account_sequence_number = nonce_response.nonce;

return Err(ServerError::NonceMismatch(e));
}
}
Err(ServerError::SubmitAnchorTransaction(e))
}
}
}
9 changes: 6 additions & 3 deletions credential-verification-service/src/api/verifier.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! Handlers for verification endpoints.
//! Handler for the verification endpoints.
use crate::types::Service;
use axum::{Json, extract::State};
use std::sync::Arc;

pub async fn verify() -> Result<String, String> {
Ok("Verified".to_owned())
pub async fn verify(_state: State<Arc<Service>>, Json(_payload): Json<bool>) -> Json<String> {
Json("ok".to_string())
}
Loading