Skip to content

Commit

Permalink
Set value
Browse files Browse the repository at this point in the history
  • Loading branch information
DOBEN committed Aug 5, 2024
1 parent e972cb3 commit 4edbcc0
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 73 deletions.
2 changes: 1 addition & 1 deletion compliant-reward-distribution/indexer/resources/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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);
132 changes: 70 additions & 62 deletions compliant-reward-distribution/indexer/src/bin/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -130,6 +132,12 @@ impl From<DatabaseError> for ServerError {
}
}

impl From<Box<bincode::ErrorKind>> for ServerError {
fn from(e: Box<bincode::ErrorKind>) -> 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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -352,9 +361,8 @@ async fn main() -> anyhow::Result<()> {
.context("Unable to get cryptographic parameters.")?
.response;

// TODO: handle unwrap
let zk_statements: Statement<ArCurve, Web3IdAttribute> =
serde_json::from_str(&app.zk_statements).unwrap();
serde_json::from_str(&app.zk_statements)?;

let state = Server {
db_pool,
Expand Down Expand Up @@ -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(&param, signer_account_info)?;
let signer = check_signature(&mut state, &param).await?;

// Check that:
// - the account creation has not expired.
Expand All @@ -435,14 +434,16 @@ async fn post_twitter_post_link(
Ok(())
}

async fn post_zk_proof(
State(mut state): State<Server>,
request: Result<Json<PostZKProofParam>, 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<ArCurve, Web3IdAttribute>,
) -> Result<ZKProofExtractedData, ServerError> {
let public_data = get_public_data(
&mut state.node_client,
state.network,
Expand Down Expand Up @@ -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<Server>,
request: Result<Json<PostZKProofParam>, 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.
Expand All @@ -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(&param, signer_account_info)?;
let signer = check_signature(&mut state, &param).await?;

// Check that the signer is an admin account.
if !state.admin_accounts.contains(&signer) {
Expand All @@ -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<T>(
param: &T,
signer_account_info: QueryResponse<AccountInfo>,
) -> Result<AccountAddress, ServerError>
async fn check_signature<T>(state: &mut Server, param: &T) -> Result<AccountAddress, ServerError>
where
T: HasSigningData + serde::Serialize,
<T as HasSigningData>::Message: serde::Serialize,
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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);
}

Expand All @@ -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(&param, signer_account_info)?;
let signer = check_signature(&mut state, &param).await?;

// Check that the signer is an admin account.
if !state.admin_accounts.contains(&signer) {
Expand Down Expand Up @@ -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(&param, signer_account_info)?;
let signer = check_signature(&mut state, &param).await?;

// Check that the signer is an admin account.
if !state.admin_accounts.contains(&signer) {
Expand Down
71 changes: 61 additions & 10 deletions compliant-reward-distribution/indexer/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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?;

Expand Down Expand Up @@ -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, &params).await?;

let claimed: bool = row.try_get("claimed")?;
let twitter_post_link_valid: Option<bool> = 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
Expand All @@ -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, &params).await?;
Expand All @@ -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, &params).await?;

let claimed: bool = row.try_get("claimed")?;
let zk_proof_valid: Option<bool> = 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, &params).await?;

Ok(())
}

Expand Down
Loading

0 comments on commit 4edbcc0

Please sign in to comment.