Skip to content

Commit

Permalink
feat(verifier-alliance-database): check that inseted verified contrac…
Browse files Browse the repository at this point in the history
…t is better than existing ones (#1214)
  • Loading branch information
rimrakhimov authored Jan 28, 2025
1 parent ad1927c commit 3bb4339
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 10 deletions.
2 changes: 1 addition & 1 deletion eth-bytecode-db/verifier-alliance-database/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
sha2 = { workspace = true }
sha3 = { workspace = true }
strum = { workspace = true }
strum = { workspace = true, features = ["std"] }
verification-common = { workspace = true }
verifier-alliance-entity = { workspace = true }

Expand Down
78 changes: 78 additions & 0 deletions eth-bytecode-db/verifier-alliance-database/src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,84 @@ pub fn try_models_into_verified_contract(
})
}

pub use compare_matches::should_store_the_match;
mod compare_matches {
use super::*;

#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
enum MatchStatus {
NoMatch,
WithoutMetadata,
WithMetadata,
}

impl From<&Match> for MatchStatus {
fn from(value: &Match) -> Self {
if value.metadata_match {
MatchStatus::WithMetadata
} else {
MatchStatus::WithoutMetadata
}
}
}

fn status_from_model_match(does_match: bool, does_metadata_match: Option<bool>) -> MatchStatus {
if !does_match {
return MatchStatus::NoMatch;
}
if let Some(true) = does_metadata_match {
return MatchStatus::WithMetadata;
}
MatchStatus::WithoutMetadata
}

pub async fn should_store_the_match<C: ConnectionTrait>(
database_connection: &C,
contract_deployment_id: Uuid,
potential_matches: &VerifiedContractMatches,
) -> Result<bool, Error> {
let (potential_creation_match, potential_runtime_match) = match potential_matches {
VerifiedContractMatches::OnlyCreation { creation_match } => {
(creation_match.into(), MatchStatus::NoMatch)
}
VerifiedContractMatches::OnlyRuntime { runtime_match } => {
(MatchStatus::NoMatch, runtime_match.into())
}
VerifiedContractMatches::Complete {
creation_match,
runtime_match,
} => (creation_match.into(), runtime_match.into()),
};

// We want to store all perfect matches even if there are other ones in the database.
// That should be impossible, but in case that happens we are interested in storing them all
// in order to manually review them later.
if potential_creation_match == MatchStatus::WithMetadata
|| potential_runtime_match == MatchStatus::WithMetadata
{
return Ok(true);
}

let is_model_worse = |model: &verified_contracts::Model| {
let model_creation_match =
status_from_model_match(model.creation_match, model.creation_metadata_match);
let model_runtime_match =
status_from_model_match(model.runtime_match, model.runtime_metadata_match);
model_creation_match < potential_creation_match
|| model_runtime_match < potential_runtime_match
};
let existing_verified_contracts = retrieve_verified_contracts_by_deployment_id(
database_connection,
contract_deployment_id,
)
.await?;
let should_potential_match_be_stored =
existing_verified_contracts.iter().all(is_model_worse);

Ok(should_potential_match_be_stored)
}
}

fn extract_match_from_model(
metadata_match: bool,
transformations: Value,
Expand Down
13 changes: 13 additions & 0 deletions eth-bytecode-db/verifier-alliance-database/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ pub async fn insert_verified_contract(
.await
.context("begin transaction")?;

// Check that the database does not already have better verification data for the deployment
if !internal::should_store_the_match(
&transaction,
verified_contract.contract_deployment_id,
&verified_contract.matches,
)
.await?
{
return Err(anyhow!(
"the candidate verified contract is not better than existing"
));
}

let sources = std::mem::take(&mut verified_contract.compiled_contract.sources);
let source_hashes = internal::precalculate_source_hashes(&sources);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
use crate::from_json;
use blockscout_display_bytes::decode_hex;
use blockscout_service_launcher::test_database::database;
use sea_orm::{prelude::Uuid, DatabaseConnection};
use pretty_assertions::assert_eq;
use sea_orm::DatabaseConnection;
use std::collections::BTreeMap;
use verification_common::verifier_alliance::{
CompilationArtifacts, CreationCodeArtifacts, Match, MatchTransformation, MatchValues,
RuntimeCodeArtifacts,
};
use verifier_alliance_database::{
CompiledContract, CompiledContractCompiler, CompiledContractLanguage, InsertContractDeployment,
VerifiedContract, VerifiedContractMatches,
CompiledContract, CompiledContractCompiler, CompiledContractLanguage, ContractDeployment,
InsertContractDeployment, VerifiedContract, VerifiedContractMatches,
};
use verifier_alliance_migration::Migrator;

#[tokio::test]
async fn insert_verified_contract_with_complete_matches_work() {
let db_guard = database!(Migrator);

let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()).await;
let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref())
.await
.id;
let compiled_contract = complete_compiled_contract();
let verified_contract = VerifiedContract {
contract_deployment_id,
Expand Down Expand Up @@ -48,7 +51,9 @@ async fn insert_verified_contract_with_complete_matches_work() {
async fn insert_verified_contract_with_runtime_only_matches_work() {
let db_guard = database!(Migrator);

let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()).await;
let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref())
.await
.id;
let compiled_contract = complete_compiled_contract();
let verified_contract = VerifiedContract {
contract_deployment_id,
Expand All @@ -74,7 +79,9 @@ async fn insert_verified_contract_with_runtime_only_matches_work() {
async fn insert_verified_contract_with_creation_only_matches_work() {
let db_guard = database!(Migrator);

let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()).await;
let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref())
.await
.id;
let compiled_contract = complete_compiled_contract();
let verified_contract = VerifiedContract {
contract_deployment_id,
Expand All @@ -100,7 +107,9 @@ async fn insert_verified_contract_with_creation_only_matches_work() {
async fn insert_verified_contract_with_filled_matches() {
let db_guard = database!(Migrator);

let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()).await;
let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref())
.await
.id;
let compiled_contract = complete_compiled_contract();

let (runtime_match_values, runtime_match_transformations) = {
Expand Down Expand Up @@ -174,6 +183,157 @@ async fn insert_verified_contract_with_filled_matches() {
.expect("error while inserting");
}

#[tokio::test]
async fn inserted_verified_contract_can_be_retrieved() {
let db_guard = database!(Migrator);
let database_connection = db_guard.client();
let database_connection = database_connection.as_ref();

let contract_deployment = insert_contract_deployment(database_connection).await;
let compiled_contract = complete_compiled_contract();
let verified_contract = VerifiedContract {
contract_deployment_id: contract_deployment.id,
compiled_contract,
matches: VerifiedContractMatches::Complete {
runtime_match: Match {
metadata_match: true,
transformations: vec![],
values: Default::default(),
},
creation_match: Match {
metadata_match: true,
transformations: vec![],
values: Default::default(),
},
},
};

verifier_alliance_database::insert_verified_contract(
database_connection,
verified_contract.clone(),
)
.await
.expect("error while inserting");

let retrieved_contracts = verifier_alliance_database::find_verified_contracts(
database_connection,
contract_deployment.chain_id,
contract_deployment.address,
)
.await
.expect("error while retrieving");
let retrieved_verified_contracts: Vec<_> = retrieved_contracts
.into_iter()
.map(|value| value.verified_contract)
.collect();
assert_eq!(
retrieved_verified_contracts,
vec![verified_contract],
"invalid retrieved values"
);
}

#[tokio::test]
async fn not_override_partial_matches() {
let db_guard = database!(Migrator);
let database_connection = db_guard.client();
let database_connection = database_connection.as_ref();

let contract_deployment = insert_contract_deployment(database_connection).await;

let partially_verified_contract = VerifiedContract {
contract_deployment_id: contract_deployment.id,
compiled_contract: complete_compiled_contract(),
matches: VerifiedContractMatches::Complete {
runtime_match: Match {
metadata_match: false,
transformations: vec![],
values: Default::default(),
},
creation_match: Match {
metadata_match: false,
transformations: vec![],
values: Default::default(),
},
},
};
verifier_alliance_database::insert_verified_contract(
database_connection,
partially_verified_contract.clone(),
)
.await
.expect("error while inserting partially verified contract");

let mut another_partially_verified_contract = partially_verified_contract.clone();
another_partially_verified_contract
.compiled_contract
.creation_code
.extend_from_slice(&[0x10]);
another_partially_verified_contract
.compiled_contract
.runtime_code
.extend_from_slice(&[0x10]);
verifier_alliance_database::insert_verified_contract(
database_connection,
another_partially_verified_contract.clone(),
)
.await
.map_err(|err| {
assert!(
err.to_string().contains("is not better than existing"),
"unexpected error: {}",
err
)
})
.expect_err("error expected while inserting another partially verified contract");

let mut fully_verified_contract = partially_verified_contract.clone();
fully_verified_contract
.compiled_contract
.creation_code
.extend_from_slice(&[0xff]);
fully_verified_contract
.compiled_contract
.runtime_code
.extend_from_slice(&[0xff]);
fully_verified_contract.matches = VerifiedContractMatches::Complete {
creation_match: Match {
metadata_match: true,
transformations: vec![],
values: Default::default(),
},
runtime_match: Match {
metadata_match: true,
transformations: vec![],
values: Default::default(),
},
};
verifier_alliance_database::insert_verified_contract(
database_connection,
fully_verified_contract.clone(),
)
.await
.expect("error while inserting fully verified contract");

let mut retrieved_contracts = verifier_alliance_database::find_verified_contracts(
database_connection,
contract_deployment.chain_id,
contract_deployment.address,
)
.await
.expect("error while retrieving");
retrieved_contracts.sort_by_key(|value| value.created_at);
let retrieved_verified_contracts: Vec<_> = retrieved_contracts
.into_iter()
.map(|value| value.verified_contract)
.collect();

assert_eq!(
retrieved_verified_contracts,
vec![partially_verified_contract, fully_verified_contract]
);
}

fn complete_compiled_contract() -> CompiledContract {
CompiledContract {
compiler: CompiledContractCompiler::Solc,
Expand Down Expand Up @@ -209,7 +369,9 @@ fn complete_compiled_contract() -> CompiledContract {
}
}

async fn insert_contract_deployment(database_connection: &DatabaseConnection) -> Uuid {
async fn insert_contract_deployment(
database_connection: &DatabaseConnection,
) -> ContractDeployment {
let contract_deployment = InsertContractDeployment::Regular {
chain_id: 10,
address: decode_hex("0x8FbB39A5a79aeCE03c8f13ccEE0b96C128ec1a67").unwrap(),
Expand All @@ -227,5 +389,4 @@ async fn insert_contract_deployment(database_connection: &DatabaseConnection) ->
verifier_alliance_database::insert_contract_deployment(database_connection, contract_deployment)
.await
.expect("error while inserting contract deployment")
.id
}

0 comments on commit 3bb4339

Please sign in to comment.