From 4edbcc05f50fdc37c4ec578dad5b09cb727cd5b9 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Mon, 5 Aug 2024 16:23:38 +0200 Subject: [PATCH] Set value --- .../indexer/resources/schema.sql | 2 +- .../indexer/src/bin/server.rs | 132 ++++++++++-------- .../indexer/src/db.rs | 71 ++++++++-- .../indexer/src/types.rs | 10 ++ 4 files changed, 142 insertions(+), 73 deletions(-) diff --git a/compliant-reward-distribution/indexer/resources/schema.sql b/compliant-reward-distribution/indexer/resources/schema.sql index adf72d1f..50f41fc5 100644 --- a/compliant-reward-distribution/indexer/resources/schema.sql +++ b/compliant-reward-distribution/indexer/resources/schema.sql @@ -77,4 +77,4 @@ CREATE TABLE IF NOT EXISTS accounts ( -- Improve performance on queries for a given account_address in the accounts table. CREATE INDEX IF NOT EXISTS accounts_index ON accounts (account_address); -- Improve performance on queries for given pending_approvals in the accounts table. -CREATE INDEX IF NOT EXISTS pending_approval_index ON accounts (pending_approval); +CREATE INDEX IF NOT EXISTS pending_approvals_index ON accounts (pending_approval); diff --git a/compliant-reward-distribution/indexer/src/bin/server.rs b/compliant-reward-distribution/indexer/src/bin/server.rs index d01d437c..e518b51e 100644 --- a/compliant-reward-distribution/indexer/src/bin/server.rs +++ b/compliant-reward-distribution/indexer/src/bin/server.rs @@ -20,13 +20,13 @@ use concordium_rust_sdk::{ id_proof_types::Statement, types::{AccountAddress, AccountCredentialWithoutProofs}, }, - types::{AbsoluteBlockHeight, AccountInfo}, - v2::{AccountIdentifier, BlockIdentifier, Client, QueryError, QueryResponse}, + types::AbsoluteBlockHeight, + v2::{AccountIdentifier, BlockIdentifier, Client, QueryError}, web3id::{ did::Network, get_public_data, CredentialLookupError, CredentialProof, CredentialStatement::{Account, Web3Id}, - PresentationVerificationError, Web3IdAttribute, + Presentation, PresentationVerificationError, Web3IdAttribute, }, }; use deadpool_postgres::PoolError; @@ -36,7 +36,7 @@ use indexer::{ types::{ AccountDataReturn, CanClaimParam, ClaimExpiryDurationDays, GetAccountDataParam, GetPendingApprovalsParam, HasSigningData, Health, PostTwitterPostLinkParam, - PostZKProofParam, SetClaimedParam, SigningData, VecAccountDataReturn, + PostZKProofParam, SetClaimedParam, SigningData, VecAccountDataReturn, ZKProofExtractedData, }, }; use sha2::Digest; @@ -112,6 +112,8 @@ pub enum ServerError { AccountExists(AbsoluteBlockHeight), #[error("Claim already expired. Your account creation has to be not older than {0}.")] ClaimExpired(ClaimExpiryDurationDays), + #[error("Converting message to bytes caused an error: {0}.")] + MessageConversion(bincode::ErrorKind), } /// Mapping DatabaseError to ServerError @@ -130,6 +132,12 @@ impl From for ServerError { } } +impl From> for ServerError { + fn from(e: Box) -> Self { + ServerError::MessageConversion(*e) + } +} + /// Check that the account is eligible for claiming the reward by checking that: /// - the account creation has not expired. /// - the account exists in the database. @@ -226,6 +234,7 @@ impl IntoResponse for ServerError { | ServerError::WrongNetwork(..) | ServerError::RevealAttribute(_) | ServerError::ClaimExpired(_) + | ServerError::MessageConversion(_) | ServerError::AccountExists(..) => { let error_message = format!("Bad request: {self}"); tracing::warn!(error_message); @@ -352,9 +361,8 @@ async fn main() -> anyhow::Result<()> { .context("Unable to get cryptographic parameters.")? .response; - // TODO: handle unwrap let zk_statements: Statement = - serde_json::from_str(&app.zk_statements).unwrap(); + serde_json::from_str(&app.zk_statements)?; let state = Server { db_pool, @@ -404,19 +412,10 @@ async fn post_twitter_post_link( ) -> Result<(), ServerError> { let Json(param) = request?; - let signer_account_info = state - .node_client - .get_account_info( - &AccountIdentifier::Address(param.signing_data.signer), - BlockIdentifier::LastFinal, - ) - .await - .map_err(ServerError::QueryError)?; - // Check that: // - the signature is valid. // - the signature is not expired. - let signer = check_signature(¶m, signer_account_info)?; + let signer = check_signature(&mut state, ¶m).await?; // Check that: // - the account creation has not expired. @@ -435,14 +434,16 @@ async fn post_twitter_post_link( Ok(()) } -async fn post_zk_proof( - State(mut state): State, - request: Result, JsonRejection>, -) -> Result<(), ServerError> { - let Json(param) = request?; - - let presentation = param.presentation; - +/// Check that the zk proof is valid by checking that: +/// - the credential statuses are active. +/// - the cryptographic proofs are valid. +/// - exactly one credential statement is present in the proof. +/// - the expected zk statements have been proven. +/// - the proof has been generated for the correct network. +async fn check_zk_proof( + state: &mut Server, + presentation: Presentation, +) -> Result { let public_data = get_public_data( &mut state.node_client, state.network, @@ -550,6 +551,35 @@ async fn post_zk_proof( // TODO check that proof is not expired -> TODO: check the challenge + Ok(ZKProofExtractedData { + national_id, + nationality, + account_address, + }) +} + +async fn post_zk_proof( + State(mut state): State, + request: Result, JsonRejection>, +) -> Result<(), ServerError> { + let Json(param) = request?; + + let presentation = param.presentation; + + // Check that: + // - the credential statuses are active. + // - the cryptographic proofs are valid. + // - exactly one credential statement is present in the proof. + // - the expected zk statements have been proven. + // - the proof has been generated for the correct network. + // Return the extracted `national_id`, `nationality` and + // `account_address` associated to the proof. + let ZKProofExtractedData { + national_id, + nationality, + account_address, + } = check_zk_proof(&mut state, presentation).await?; + // Check that: // - the account creation has not expired. // - the account exists in the database. @@ -573,19 +603,10 @@ async fn set_claimed( ) -> Result<(), ServerError> { let Json(param) = request?; - let signer_account_info = state - .node_client - .get_account_info( - &AccountIdentifier::Address(param.signing_data.signer), - BlockIdentifier::LastFinal, - ) - .await - .map_err(ServerError::QueryError)?; - // Check that: // - the signature is valid. // - the signature is not expired. - let signer = check_signature(¶m, signer_account_info)?; + let signer = check_signature(&mut state, ¶m).await?; // Check that the signer is an admin account. if !state.admin_accounts.contains(&signer) { @@ -602,10 +623,7 @@ async fn set_claimed( /// Check that the signer account has signed the message by checking that: /// - the signature is valid. /// - the signature is not expired. -fn check_signature( - param: &T, - signer_account_info: QueryResponse, -) -> Result +async fn check_signature(state: &mut Server, param: &T) -> Result where T: HasSigningData + serde::Serialize, ::Message: serde::Serialize, @@ -616,6 +634,15 @@ where signature, } = param.signing_data(); + let signer_account_info = state + .node_client + .get_account_info( + &AccountIdentifier::Address(*signer), + BlockIdentifier::LastFinal, + ) + .await + .map_err(ServerError::QueryError)?; + // This backend checks that the signer account has signed the "block_hash" and "block_number" // of a block that is not older than 10 blocks from the most recent block. // Signing the "block_hash" ensures that the signature expires after 10 blocks. @@ -650,8 +677,7 @@ where // Calculate the message hash. - // TODO: better handling of unwrap. - let message_bytes = bincode::serialize(&message).unwrap(); + let message_bytes = bincode::serialize(&message)?; let message_hash = sha2::Sha256::digest([&msg_prepend[0..40], &message_bytes].concat()); // We use regular accounts as admin accounts. @@ -669,10 +695,10 @@ where AccountCredentialWithoutProofs::Normal { cdv, .. } => &cdv.cred_key_info.keys[&KeyIndex(0)], }; - let valid_signature = signer_public_key.verify(message_hash, signature); + let is_valid = signer_public_key.verify(message_hash, signature); // Check validity of the signature. - if !valid_signature { + if !is_valid { return Err(ServerError::InvalidSignature); } @@ -691,19 +717,10 @@ async fn get_account_data( let Json(param) = request?; - let signer_account_info = state - .node_client - .get_account_info( - &AccountIdentifier::Address(param.signing_data.signer), - BlockIdentifier::LastFinal, - ) - .await - .map_err(ServerError::QueryError)?; - // Check that: // - the signature is valid. // - the signature is not expired. - let signer = check_signature(¶m, signer_account_info)?; + let signer = check_signature(&mut state, ¶m).await?; // Check that the signer is an admin account. if !state.admin_accounts.contains(&signer) { @@ -741,19 +758,10 @@ async fn get_pending_approvals( return Err(ServerError::MaxRequestLimit(MAX_REQUEST_LIMIT)); } - let signer_account_info = state - .node_client - .get_account_info( - &AccountIdentifier::Address(param.signing_data.signer), - BlockIdentifier::LastFinal, - ) - .await - .map_err(ServerError::QueryError)?; - // Check that: // - the signature is valid. // - the signature is not expired. - let signer = check_signature(¶m, signer_account_info)?; + let signer = check_signature(&mut state, ¶m).await?; // Check that the signer is an admin account. if !state.admin_accounts.contains(&signer) { diff --git a/compliant-reward-distribution/indexer/src/db.rs b/compliant-reward-distribution/indexer/src/db.rs index 9b2ae180..d1124f7b 100644 --- a/compliant-reward-distribution/indexer/src/db.rs +++ b/compliant-reward-distribution/indexer/src/db.rs @@ -14,8 +14,7 @@ use tokio_postgres::{types::ToSql, NoTls}; /// TODO(maybe): add check when `setClaimed` if account is not in database. /// TODO(maybe): add timestamp when ZK proof, twitter link was submitted -/// TODO add one setter functions for all values in database (even if not used) -/// + #[derive(Debug, Error)] pub enum ConversionError { #[error("Incorrect length")] @@ -220,7 +219,7 @@ impl Database { genesis_block_hash: &BlockHash, start_block_height: AbsoluteBlockHeight, ) -> DatabaseResult<()> { - let conflict_check_query = "SELECT * FROM settings WHERE id = true"; + let conflict_check_query = "SELECT id FROM settings WHERE id = true"; let opt_row = self.client.query_opt(conflict_check_query, &[]).await?; @@ -253,6 +252,31 @@ impl Database { account_address: AccountAddress, current_zk_proof_verification_version: u16, ) -> DatabaseResult<()> { + // Check if we need to update `pending_approval` to true. + let get_account_data = self + .client + .prepare_cached( + "SELECT claimed, twitter_post_link_valid, pending_approval + FROM accounts + WHERE account_address = $1", + ) + .await?; + + let params: [&(dyn ToSql + Sync); 1] = [&(account_address.0.as_ref())]; + let row = self.client.query_one(&get_account_data, ¶ms).await?; + + let claimed: bool = row.try_get("claimed")?; + let twitter_post_link_valid: Option = row.try_get("twitter_post_link_valid")?; + let mut pending_approval: bool = row.try_get("pending_approval")?; + + if let Some(twitter_post_link_valid) = twitter_post_link_valid { + if !claimed && twitter_post_link_valid { + // If the account has submitted a twitter post link already and can still claim, + // set the `pending_approval` to true. + pending_approval = true + } + } + // Create an `uniqueness_hash` to identify the identity associated with the account // by hashing the concatenating string of `national_id` and `nationality`. // Every identity should only be allowed to receive rewards once @@ -268,20 +292,20 @@ impl Database { let uniqueness_hash = hasher.finalize(); // TODO check hash not used in database so far - // TODO if `hash` and `twitter` is set, make `pending_approval` to true let set_zk_proof = self .client .prepare_cached( "UPDATE accounts \ - SET zk_proof_valid = $1, zk_proof_verification_version = $2, uniqueness_hash = $3 \ - WHERE account_address = $4 ", + SET zk_proof_valid = $1, zk_proof_verification_version = $2, uniqueness_hash = $3, pending_approval = $4 \ + WHERE account_address = $5 ", ) .await?; - let params: [&(dyn ToSql + Sync); 4] = [ + let params: [&(dyn ToSql + Sync); 5] = [ &true, &(current_zk_proof_verification_version as i64), &uniqueness_hash.as_slice(), + &pending_approval, &account_address.0.as_ref(), ]; self.client.execute(&set_zk_proof, ¶ms).await?; @@ -297,21 +321,48 @@ impl Database { account_address: AccountAddress, current_twitter_post_link_verification_version: u16, ) -> DatabaseResult<()> { + // Check if we need to update `pending_approval` to true. + let get_account_data = self + .client + .prepare_cached( + "SELECT claimed, zk_proof_valid, pending_approval + FROM accounts + WHERE account_address = $1", + ) + .await?; + + let params: [&(dyn ToSql + Sync); 1] = [&(account_address.0.as_ref())]; + let row = self.client.query_one(&get_account_data, ¶ms).await?; + + let claimed: bool = row.try_get("claimed")?; + let zk_proof_valid: Option = row.try_get("zk_proof_valid")?; + let mut pending_approval: bool = row.try_get("pending_approval")?; + + if let Some(zk_proof_valid) = zk_proof_valid { + if !claimed && zk_proof_valid { + // If the account has submitted a ZK proof already and can still claim, + // set the `pending_approval` to true. + pending_approval = true + } + } + let set_twitter_post_link = self .client .prepare_cached( "UPDATE accounts \ - SET twitter_post_link_valid = $1, twitter_post_link_verification_version = $2, twitter_post_link = $3 \ - WHERE account_address = $4 ", + SET twitter_post_link_valid = $1, twitter_post_link_verification_version = $2, twitter_post_link = $3, pending_approval = $4 \ + WHERE account_address = $5", ) .await?; - let params: [&(dyn ToSql + Sync); 4] = [ + let params: [&(dyn ToSql + Sync); 5] = [ &true, &(current_twitter_post_link_verification_version as i64), &tweet_post_link.as_bytes(), + &pending_approval, &account_address.0.as_ref(), ]; self.client.execute(&set_twitter_post_link, ¶ms).await?; + Ok(()) } diff --git a/compliant-reward-distribution/indexer/src/types.rs b/compliant-reward-distribution/indexer/src/types.rs index fd85c888..f6047ebb 100644 --- a/compliant-reward-distribution/indexer/src/types.rs +++ b/compliant-reward-distribution/indexer/src/types.rs @@ -64,6 +64,16 @@ pub struct PostZKProofParam { pub presentation: Presentation, } +/// Helper type +pub struct ZKProofExtractedData { + /// + pub national_id: String, + /// + pub nationality: String, + /// + pub account_address: AccountAddress, +} + #[derive(serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct TwitterPostLinkMessage {