Skip to content

Commit 3bb4339

Browse files
authored
feat(verifier-alliance-database): check that inseted verified contract is better than existing ones (#1214)
1 parent ad1927c commit 3bb4339

File tree

4 files changed

+262
-10
lines changed

4 files changed

+262
-10
lines changed

eth-bytecode-db/verifier-alliance-database/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ serde = { workspace = true }
1212
serde_json = { workspace = true }
1313
sha2 = { workspace = true }
1414
sha3 = { workspace = true }
15-
strum = { workspace = true }
15+
strum = { workspace = true, features = ["std"] }
1616
verification-common = { workspace = true }
1717
verifier-alliance-entity = { workspace = true }
1818

eth-bytecode-db/verifier-alliance-database/src/internal.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,84 @@ pub fn try_models_into_verified_contract(
550550
})
551551
}
552552

553+
pub use compare_matches::should_store_the_match;
554+
mod compare_matches {
555+
use super::*;
556+
557+
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
558+
enum MatchStatus {
559+
NoMatch,
560+
WithoutMetadata,
561+
WithMetadata,
562+
}
563+
564+
impl From<&Match> for MatchStatus {
565+
fn from(value: &Match) -> Self {
566+
if value.metadata_match {
567+
MatchStatus::WithMetadata
568+
} else {
569+
MatchStatus::WithoutMetadata
570+
}
571+
}
572+
}
573+
574+
fn status_from_model_match(does_match: bool, does_metadata_match: Option<bool>) -> MatchStatus {
575+
if !does_match {
576+
return MatchStatus::NoMatch;
577+
}
578+
if let Some(true) = does_metadata_match {
579+
return MatchStatus::WithMetadata;
580+
}
581+
MatchStatus::WithoutMetadata
582+
}
583+
584+
pub async fn should_store_the_match<C: ConnectionTrait>(
585+
database_connection: &C,
586+
contract_deployment_id: Uuid,
587+
potential_matches: &VerifiedContractMatches,
588+
) -> Result<bool, Error> {
589+
let (potential_creation_match, potential_runtime_match) = match potential_matches {
590+
VerifiedContractMatches::OnlyCreation { creation_match } => {
591+
(creation_match.into(), MatchStatus::NoMatch)
592+
}
593+
VerifiedContractMatches::OnlyRuntime { runtime_match } => {
594+
(MatchStatus::NoMatch, runtime_match.into())
595+
}
596+
VerifiedContractMatches::Complete {
597+
creation_match,
598+
runtime_match,
599+
} => (creation_match.into(), runtime_match.into()),
600+
};
601+
602+
// We want to store all perfect matches even if there are other ones in the database.
603+
// That should be impossible, but in case that happens we are interested in storing them all
604+
// in order to manually review them later.
605+
if potential_creation_match == MatchStatus::WithMetadata
606+
|| potential_runtime_match == MatchStatus::WithMetadata
607+
{
608+
return Ok(true);
609+
}
610+
611+
let is_model_worse = |model: &verified_contracts::Model| {
612+
let model_creation_match =
613+
status_from_model_match(model.creation_match, model.creation_metadata_match);
614+
let model_runtime_match =
615+
status_from_model_match(model.runtime_match, model.runtime_metadata_match);
616+
model_creation_match < potential_creation_match
617+
|| model_runtime_match < potential_runtime_match
618+
};
619+
let existing_verified_contracts = retrieve_verified_contracts_by_deployment_id(
620+
database_connection,
621+
contract_deployment_id,
622+
)
623+
.await?;
624+
let should_potential_match_be_stored =
625+
existing_verified_contracts.iter().all(is_model_worse);
626+
627+
Ok(should_potential_match_be_stored)
628+
}
629+
}
630+
553631
fn extract_match_from_model(
554632
metadata_match: bool,
555633
transformations: Value,

eth-bytecode-db/verifier-alliance-database/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ pub async fn insert_verified_contract(
6262
.await
6363
.context("begin transaction")?;
6464

65+
// Check that the database does not already have better verification data for the deployment
66+
if !internal::should_store_the_match(
67+
&transaction,
68+
verified_contract.contract_deployment_id,
69+
&verified_contract.matches,
70+
)
71+
.await?
72+
{
73+
return Err(anyhow!(
74+
"the candidate verified contract is not better than existing"
75+
));
76+
}
77+
6578
let sources = std::mem::take(&mut verified_contract.compiled_contract.sources);
6679
let source_hashes = internal::precalculate_source_hashes(&sources);
6780

eth-bytecode-db/verifier-alliance-database/tests/integration/verified_contracts.rs

Lines changed: 170 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
use crate::from_json;
22
use blockscout_display_bytes::decode_hex;
33
use blockscout_service_launcher::test_database::database;
4-
use sea_orm::{prelude::Uuid, DatabaseConnection};
4+
use pretty_assertions::assert_eq;
5+
use sea_orm::DatabaseConnection;
56
use std::collections::BTreeMap;
67
use verification_common::verifier_alliance::{
78
CompilationArtifacts, CreationCodeArtifacts, Match, MatchTransformation, MatchValues,
89
RuntimeCodeArtifacts,
910
};
1011
use verifier_alliance_database::{
11-
CompiledContract, CompiledContractCompiler, CompiledContractLanguage, InsertContractDeployment,
12-
VerifiedContract, VerifiedContractMatches,
12+
CompiledContract, CompiledContractCompiler, CompiledContractLanguage, ContractDeployment,
13+
InsertContractDeployment, VerifiedContract, VerifiedContractMatches,
1314
};
1415
use verifier_alliance_migration::Migrator;
1516

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

20-
let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()).await;
21+
let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref())
22+
.await
23+
.id;
2124
let compiled_contract = complete_compiled_contract();
2225
let verified_contract = VerifiedContract {
2326
contract_deployment_id,
@@ -48,7 +51,9 @@ async fn insert_verified_contract_with_complete_matches_work() {
4851
async fn insert_verified_contract_with_runtime_only_matches_work() {
4952
let db_guard = database!(Migrator);
5053

51-
let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()).await;
54+
let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref())
55+
.await
56+
.id;
5257
let compiled_contract = complete_compiled_contract();
5358
let verified_contract = VerifiedContract {
5459
contract_deployment_id,
@@ -74,7 +79,9 @@ async fn insert_verified_contract_with_runtime_only_matches_work() {
7479
async fn insert_verified_contract_with_creation_only_matches_work() {
7580
let db_guard = database!(Migrator);
7681

77-
let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()).await;
82+
let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref())
83+
.await
84+
.id;
7885
let compiled_contract = complete_compiled_contract();
7986
let verified_contract = VerifiedContract {
8087
contract_deployment_id,
@@ -100,7 +107,9 @@ async fn insert_verified_contract_with_creation_only_matches_work() {
100107
async fn insert_verified_contract_with_filled_matches() {
101108
let db_guard = database!(Migrator);
102109

103-
let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()).await;
110+
let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref())
111+
.await
112+
.id;
104113
let compiled_contract = complete_compiled_contract();
105114

106115
let (runtime_match_values, runtime_match_transformations) = {
@@ -174,6 +183,157 @@ async fn insert_verified_contract_with_filled_matches() {
174183
.expect("error while inserting");
175184
}
176185

186+
#[tokio::test]
187+
async fn inserted_verified_contract_can_be_retrieved() {
188+
let db_guard = database!(Migrator);
189+
let database_connection = db_guard.client();
190+
let database_connection = database_connection.as_ref();
191+
192+
let contract_deployment = insert_contract_deployment(database_connection).await;
193+
let compiled_contract = complete_compiled_contract();
194+
let verified_contract = VerifiedContract {
195+
contract_deployment_id: contract_deployment.id,
196+
compiled_contract,
197+
matches: VerifiedContractMatches::Complete {
198+
runtime_match: Match {
199+
metadata_match: true,
200+
transformations: vec![],
201+
values: Default::default(),
202+
},
203+
creation_match: Match {
204+
metadata_match: true,
205+
transformations: vec![],
206+
values: Default::default(),
207+
},
208+
},
209+
};
210+
211+
verifier_alliance_database::insert_verified_contract(
212+
database_connection,
213+
verified_contract.clone(),
214+
)
215+
.await
216+
.expect("error while inserting");
217+
218+
let retrieved_contracts = verifier_alliance_database::find_verified_contracts(
219+
database_connection,
220+
contract_deployment.chain_id,
221+
contract_deployment.address,
222+
)
223+
.await
224+
.expect("error while retrieving");
225+
let retrieved_verified_contracts: Vec<_> = retrieved_contracts
226+
.into_iter()
227+
.map(|value| value.verified_contract)
228+
.collect();
229+
assert_eq!(
230+
retrieved_verified_contracts,
231+
vec![verified_contract],
232+
"invalid retrieved values"
233+
);
234+
}
235+
236+
#[tokio::test]
237+
async fn not_override_partial_matches() {
238+
let db_guard = database!(Migrator);
239+
let database_connection = db_guard.client();
240+
let database_connection = database_connection.as_ref();
241+
242+
let contract_deployment = insert_contract_deployment(database_connection).await;
243+
244+
let partially_verified_contract = VerifiedContract {
245+
contract_deployment_id: contract_deployment.id,
246+
compiled_contract: complete_compiled_contract(),
247+
matches: VerifiedContractMatches::Complete {
248+
runtime_match: Match {
249+
metadata_match: false,
250+
transformations: vec![],
251+
values: Default::default(),
252+
},
253+
creation_match: Match {
254+
metadata_match: false,
255+
transformations: vec![],
256+
values: Default::default(),
257+
},
258+
},
259+
};
260+
verifier_alliance_database::insert_verified_contract(
261+
database_connection,
262+
partially_verified_contract.clone(),
263+
)
264+
.await
265+
.expect("error while inserting partially verified contract");
266+
267+
let mut another_partially_verified_contract = partially_verified_contract.clone();
268+
another_partially_verified_contract
269+
.compiled_contract
270+
.creation_code
271+
.extend_from_slice(&[0x10]);
272+
another_partially_verified_contract
273+
.compiled_contract
274+
.runtime_code
275+
.extend_from_slice(&[0x10]);
276+
verifier_alliance_database::insert_verified_contract(
277+
database_connection,
278+
another_partially_verified_contract.clone(),
279+
)
280+
.await
281+
.map_err(|err| {
282+
assert!(
283+
err.to_string().contains("is not better than existing"),
284+
"unexpected error: {}",
285+
err
286+
)
287+
})
288+
.expect_err("error expected while inserting another partially verified contract");
289+
290+
let mut fully_verified_contract = partially_verified_contract.clone();
291+
fully_verified_contract
292+
.compiled_contract
293+
.creation_code
294+
.extend_from_slice(&[0xff]);
295+
fully_verified_contract
296+
.compiled_contract
297+
.runtime_code
298+
.extend_from_slice(&[0xff]);
299+
fully_verified_contract.matches = VerifiedContractMatches::Complete {
300+
creation_match: Match {
301+
metadata_match: true,
302+
transformations: vec![],
303+
values: Default::default(),
304+
},
305+
runtime_match: Match {
306+
metadata_match: true,
307+
transformations: vec![],
308+
values: Default::default(),
309+
},
310+
};
311+
verifier_alliance_database::insert_verified_contract(
312+
database_connection,
313+
fully_verified_contract.clone(),
314+
)
315+
.await
316+
.expect("error while inserting fully verified contract");
317+
318+
let mut retrieved_contracts = verifier_alliance_database::find_verified_contracts(
319+
database_connection,
320+
contract_deployment.chain_id,
321+
contract_deployment.address,
322+
)
323+
.await
324+
.expect("error while retrieving");
325+
retrieved_contracts.sort_by_key(|value| value.created_at);
326+
let retrieved_verified_contracts: Vec<_> = retrieved_contracts
327+
.into_iter()
328+
.map(|value| value.verified_contract)
329+
.collect();
330+
331+
assert_eq!(
332+
retrieved_verified_contracts,
333+
vec![partially_verified_contract, fully_verified_contract]
334+
);
335+
}
336+
177337
fn complete_compiled_contract() -> CompiledContract {
178338
CompiledContract {
179339
compiler: CompiledContractCompiler::Solc,
@@ -209,7 +369,9 @@ fn complete_compiled_contract() -> CompiledContract {
209369
}
210370
}
211371

212-
async fn insert_contract_deployment(database_connection: &DatabaseConnection) -> Uuid {
372+
async fn insert_contract_deployment(
373+
database_connection: &DatabaseConnection,
374+
) -> ContractDeployment {
213375
let contract_deployment = InsertContractDeployment::Regular {
214376
chain_id: 10,
215377
address: decode_hex("0x8FbB39A5a79aeCE03c8f13ccEE0b96C128ec1a67").unwrap(),
@@ -227,5 +389,4 @@ async fn insert_contract_deployment(database_connection: &DatabaseConnection) ->
227389
verifier_alliance_database::insert_contract_deployment(database_connection, contract_deployment)
228390
.await
229391
.expect("error while inserting contract deployment")
230-
.id
231392
}

0 commit comments

Comments
 (0)