Skip to content

Commit a4e27bd

Browse files
committed
feat: fix verifier
1 parent 2fe7b60 commit a4e27bd

File tree

6 files changed

+120
-154
lines changed

6 files changed

+120
-154
lines changed

fendermint/app/src/cmd/proof_cache.rs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ fn inspect_cache(db_path: &Path) -> anyhow::Result<()> {
5757

5858
println!(
5959
"{:<12} {:<20?} {:<15} {:<15}",
60-
entry.instance_id,
61-
entry.finalized_epochs,
60+
entry.certificate.gpbft_instance,
61+
entry.certificate.ec_chain.suffix(),
6262
format!("{} bytes", proof_size),
6363
format!("{} signers", entry.certificate.signers.len())
6464
);
@@ -85,8 +85,14 @@ fn show_stats(db_path: &Path) -> anyhow::Result<()> {
8585
println!("Last Committed: {:?}", last_committed);
8686
println!(
8787
"Instances: {} - {}",
88-
entries.first().map(|e| e.instance_id).unwrap_or(0),
89-
entries.last().map(|e| e.instance_id).unwrap_or(0)
88+
entries
89+
.first()
90+
.map(|e| e.certificate.gpbft_instance)
91+
.unwrap_or(0),
92+
entries
93+
.last()
94+
.map(|e| e.certificate.gpbft_instance)
95+
.unwrap_or(0)
9096
);
9197
println!();
9298

@@ -138,9 +144,8 @@ fn get_proof(db_path: &Path, instance_id: u64) -> anyhow::Result<()> {
138144
println!(" Instance ID: {}", entry.certificate.gpbft_instance);
139145
println!(
140146
" Finalized Epochs: {:?}",
141-
&entry.certificate.finalized_epochs()
147+
&entry.certificate.ec_chain.suffix()
142148
);
143-
println!(" Power Table CID: {}", entry.certificate.power_table_delta);
144149
println!(
145150
" BLS Signature: {} bytes",
146151
entry.certificate.signature.len()
@@ -158,13 +163,18 @@ fn get_proof(db_path: &Path, instance_id: u64) -> anyhow::Result<()> {
158163
proof_bundle_size,
159164
proof_bundle_size as f64 / 1024.0
160165
);
161-
println!(
162-
" Storage Proofs: {}",
163-
entry.proof_bundle.storage_proofs.len()
164-
);
165-
println!(" Event Proofs: {}", entry.proof_bundle.event_proofs.len());
166-
println!(" Witness Blocks: {}", entry.proof_bundle.blocks.len());
167-
println!();
166+
167+
if let Some(proof_bundle) = &entry.proof_bundle {
168+
println!(
169+
" Storage Proofs: {}",
170+
entry.proof_bundle.storage_proofs.len()
171+
);
172+
println!(" Event Proofs: {}", entry.proof_bundle.event_proofs.len());
173+
println!(" Witness Blocks: {}", entry.proof_bundle.blocks.len());
174+
println!();
175+
} else {
176+
println!(" No proof bundle found");
177+
}
168178

169179
// Metadata
170180
println!("Metadata:");

fendermint/vm/topdown/proof-service/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fendermint_vm_topdown_proof_service"
3-
description = "Proof generator service for F3-based parent finality"
3+
description = "Proof generator service"
44
version = "0.1.0"
55
edition.workspace = true
66
license.workspace = true

fendermint/vm/topdown/proof-service/src/assembler.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ use url::Url;
2929
/// Event signature for NewTopDownMessage from LibGateway.sol
3030
/// Event: NewTopDownMessage(address indexed subnet, IpcEnvelope message, bytes32 indexed id)
3131
/// Bindings: contract_bindings::lib_gateway::NewTopDownMessageFilter
32-
const NEW_TOPDOWN_MESSAGE_SIGNATURE: &str = "NewTopDownMessage(address,IpcEnvelope,bytes32)";
32+
pub const NEW_TOPDOWN_MESSAGE_SIGNATURE: &str = "NewTopDownMessage(address,IpcEnvelope,bytes32)";
3333

3434
/// Event signature for NewPowerChangeRequest from LibPowerChangeLog.sol
3535
/// Event: NewPowerChangeRequest(PowerOperation op, address validator, bytes payload, uint64 configurationNumber)
3636
/// Bindings: contract_bindings::lib_power_change_log::NewPowerChangeRequestFilter
3737
/// This captures validator power changes that need to be reflected in the subnet
38-
const NEW_POWER_CHANGE_REQUEST_SIGNATURE: &str =
38+
pub const NEW_POWER_CHANGE_REQUEST_SIGNATURE: &str =
3939
"NewPowerChangeRequest(PowerOperation,address,bytes,uint64)";
4040

4141
/// Storage slot offset for topDownNonce in the Subnet struct

fendermint/vm/topdown/proof-service/src/config.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use ipc_api::subnet_id::SubnetID;
66
use serde::{Deserialize, Serialize};
77
use std::time::Duration;
88

9+
const FILECOIN_MAINNET_CHAIN_ID: u64 = 314;
10+
const FILECOIN_CALIBRATION_CHAIN_ID: u64 = 314159;
11+
912
/// Represents a value that can be either a numeric Actor ID or an Ethereum address string.
1013
#[derive(Debug, Clone, Serialize, Deserialize)]
1114
#[serde(untagged)]
@@ -50,8 +53,8 @@ impl ProofServiceConfig {
5053
let root_id = self.subnet_id.root_id();
5154

5255
match root_id {
53-
314 => "mainnet".to_string(),
54-
314159 => "calibrationnet".to_string(),
56+
FILECOIN_MAINNET_CHAIN_ID => "mainnet".to_string(),
57+
FILECOIN_CALIBRATION_CHAIN_ID => "calibrationnet".to_string(),
5558
_ => {
5659
tracing::warn!(
5760
root_id,

fendermint/vm/topdown/proof-service/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub use cache::ProofCache;
2323
pub use config::{CacheConfig, ProofServiceConfig};
2424
pub use service::ProofGeneratorService;
2525
pub use types::{CacheEntry, SerializableF3Certificate};
26-
pub use verifier::verify_proof_bundle;
26+
pub use verifier::ProofsVerifier;
2727

2828
use anyhow::{Context, Result};
2929
use std::sync::Arc;

fendermint/vm/topdown/proof-service/src/verifier.rs

Lines changed: 88 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -5,151 +5,104 @@
55
//! Provides deterministic verification of proof bundles against F3 certificates.
66
//! Used by validators during block attestation to verify parent finality proofs.
77
8-
use crate::types::SerializableF3Certificate;
9-
use anyhow::{Context, Result};
10-
use proofs::proofs::common::bundle::{ProofBlock, UnifiedProofBundle};
11-
use proofs::proofs::storage::{bundle::StorageProof, verifier::verify_storage_proof};
12-
use tracing::debug;
13-
14-
/// Verify a unified proof bundle against a certificate
15-
///
16-
/// This performs deterministic verification of:
17-
/// - Storage proofs (contract state at parent height)
18-
/// - Event proofs (emitted events at parent height)
19-
///
20-
/// # Arguments
21-
/// * `bundle` - The proof bundle to verify
22-
/// * `certificate` - The certificate containing finalized epochs
23-
///
24-
/// # Returns
25-
/// `Ok(())` if all proofs are valid, `Err` otherwise
26-
///
27-
/// # Usage in Block Attestation
28-
///
29-
/// ```ignore
30-
/// // When validator receives block with parent finality data
31-
/// if cache.contains(cert.instance_id) {
32-
/// // Already validated - just verify proofs
33-
/// let cached = cache.get(cert.instance_id).unwrap();
34-
/// verify_proof_bundle(&cached.proof_bundle, &cached.certificate)?;
35-
/// } else {
36-
/// // Not cached - need full crypto validation first
37-
/// let validated = f3_client.fetch_and_validate(cert.instance_id).await?;
38-
/// let serializable_cert = SerializableF3Certificate::from(&certificate);
39-
/// verify_proof_bundle(&proof_bundle, &serializable_cert)?;
40-
/// }
41-
/// ```
42-
pub fn verify_proof_bundle(
43-
bundle: &UnifiedProofBundle,
44-
certificate: &SerializableF3Certificate,
45-
) -> Result<()> {
46-
debug!(
47-
gpbft_instance = certificate.gpbft_instance,
48-
storage_proofs = bundle.storage_proofs.len(),
49-
event_proofs = bundle.event_proofs.len(),
50-
witness_blocks = bundle.blocks.len(),
51-
"Verifying proof bundle"
52-
);
53-
54-
// Verify all storage proofs
55-
for (idx, storage_proof) in bundle.storage_proofs.iter().enumerate() {
56-
verify_storage_proof_internal(storage_proof, &bundle.blocks, certificate)
57-
.with_context(|| format!("Storage proof {} failed verification", idx))?;
58-
}
59-
60-
// Event proof verification uses a bundle-level API
61-
// For now, we verify that the bundle structure is valid
62-
// Full event proof verification will be added when the proofs library API is finalized
63-
if !bundle.event_proofs.is_empty() {
64-
debug!(
65-
event_proofs = bundle.event_proofs.len(),
66-
"Event proofs present (verification to be implemented with proofs library API)"
67-
);
68-
}
69-
70-
debug!(
71-
gpbft_instance = certificate.gpbft_instance,
72-
"Proof bundle verified successfully"
73-
);
74-
75-
Ok(())
8+
use crate::assembler::{NEW_POWER_CHANGE_REQUEST_SIGNATURE, NEW_TOPDOWN_MESSAGE_SIGNATURE};
9+
use anyhow::Result;
10+
use cid::Cid;
11+
use filecoin_f3_certs::FinalityCertificate;
12+
use proofs::proofs::common::bundle::{UnifiedProofBundle, UnifiedVerificationResult};
13+
use proofs::proofs::events::bundle::EventProofBundle;
14+
use proofs::proofs::events::verifier::verify_event_proof;
15+
use proofs::proofs::storage::verifier::verify_storage_proof;
16+
17+
use proofs::proofs::common::evm::{ascii_to_bytes32, extract_evm_log, hash_event_signature};
18+
19+
pub struct ProofsVerifier {
20+
events: Vec<Vec<[u8; 32]>>,
7621
}
7722

78-
/// Verify a single storage proof
79-
///
80-
/// Uses the proofs library's verify_storage_proof function with the witness blocks.
81-
fn verify_storage_proof_internal(
82-
proof: &StorageProof,
83-
blocks: &[ProofBlock],
84-
certificate: &SerializableF3Certificate,
85-
) -> Result<()> {
86-
// Get finalized epochs from certificate
87-
let finalized_epochs = certificate.finalized_epochs();
88-
89-
// Verify the proof's child epoch is in the certificate's finalized epochs
90-
let child_epoch = proof.child_epoch;
91-
if !finalized_epochs.contains(&child_epoch) {
92-
anyhow::bail!(
93-
"Storage proof child epoch {} not in certificate's finalized epochs",
94-
child_epoch
95-
);
96-
}
97-
98-
// Use the proofs library to verify the storage proof
99-
// The is_trusted_child_header function checks if the child epoch is finalized
100-
let is_trusted = |epoch: i64, _cid: &cid::Cid| -> bool { finalized_epochs.contains(&epoch) };
101-
102-
let valid = verify_storage_proof(proof, blocks, &is_trusted)
103-
.context("Storage proof verification failed")?;
23+
impl ProofsVerifier {
24+
pub fn new(subnet_id: String) -> Self {
25+
let events = vec![
26+
vec![
27+
hash_event_signature(NEW_TOPDOWN_MESSAGE_SIGNATURE),
28+
ascii_to_bytes32(&subnet_id),
29+
],
30+
vec![hash_event_signature(NEW_POWER_CHANGE_REQUEST_SIGNATURE)],
31+
];
10432

105-
if !valid {
106-
anyhow::bail!("Storage proof is invalid");
33+
Self { events }
10734
}
108-
109-
Ok(())
11035
}
11136

112-
#[cfg(test)]
113-
mod tests {
114-
use super::*;
115-
use proofs::proofs::common::bundle::UnifiedProofBundle;
116-
117-
#[test]
118-
fn test_verify_empty_bundle() {
119-
let bundle = UnifiedProofBundle {
120-
storage_proofs: vec![],
121-
event_proofs: vec![],
122-
blocks: vec![],
37+
impl ProofsVerifier {
38+
/// Verify a unified proof bundle against a certificate
39+
///
40+
/// This performs deterministic verification of:
41+
/// - Storage proofs (contract state at parent height)
42+
/// - Event proofs (emitted events at parent height)
43+
///
44+
/// # Arguments
45+
/// * `bundle` - The proof bundle to verify
46+
/// * `certificate` - The certificate containing finalized epochs
47+
pub fn verify_proof_bundle(
48+
&self,
49+
bundle: &UnifiedProofBundle,
50+
certificate: &FinalityCertificate,
51+
) -> Result<UnifiedVerificationResult> {
52+
let tipset_verifier = |epoch: i64, cid: &Cid| -> bool {
53+
certificate
54+
.ec_chain
55+
.iter()
56+
.any(|ts| ts.epoch == epoch && ts.key == cid.to_bytes())
12357
};
12458

125-
use crate::types::{SerializableECChainEntry, SerializableSupplementalData};
59+
// Verify storage proofs
60+
let mut storage_results = Vec::new();
61+
for proof in &bundle.storage_proofs {
62+
let result = verify_storage_proof(proof, &bundle.blocks, &tipset_verifier)?;
63+
storage_results.push(result);
64+
}
65+
66+
// Verify event proofs - need to create an EventProofBundle for the verifier
67+
let event_bundle = EventProofBundle {
68+
proofs: bundle.event_proofs.clone(),
69+
blocks: bundle.blocks.clone(),
70+
};
12671

127-
let cert = SerializableF3Certificate {
128-
gpbft_instance: 1,
129-
ec_chain: vec![
130-
SerializableECChainEntry {
131-
epoch: 100,
132-
key: vec![],
133-
power_table: "test_cid".to_string(),
134-
commitments: vec![0u8; 32],
135-
},
136-
SerializableECChainEntry {
137-
epoch: 101,
138-
key: vec![],
139-
power_table: "test_cid".to_string(),
140-
commitments: vec![0u8; 32],
141-
},
142-
],
143-
supplemental_data: SerializableSupplementalData {
144-
power_table: "test_cid".to_string(),
145-
commitments: vec![0u8; 32],
146-
},
147-
signature: vec![],
148-
signers: vec![],
149-
power_table_delta: vec![],
72+
// TODO Karel - fix the library to take a single CID
73+
let parent_tipset_verifier = |epoch: i64, cids: &[Cid]| -> bool {
74+
certificate
75+
.ec_chain
76+
.iter()
77+
.any(|ts| ts.epoch == epoch && ts.key == cids.first().unwrap().to_bytes())
15078
};
15179

152-
// Empty bundle should verify successfully
153-
assert!(verify_proof_bundle(&bundle, &cert).is_ok());
80+
let event_results = verify_event_proof(
81+
&event_bundle,
82+
&parent_tipset_verifier,
83+
&tipset_verifier,
84+
Some(&self.create_event_filter()),
85+
)?;
86+
87+
Ok(UnifiedVerificationResult {
88+
storage_results,
89+
event_results,
90+
})
91+
}
92+
93+
fn create_event_filter(&self) -> impl Fn(&fvm_shared::event::ActorEvent) -> bool + '_ {
94+
|ev: &fvm_shared::event::ActorEvent| -> bool {
95+
if let Some(log) = extract_evm_log(ev) {
96+
self.events.iter().any(|expected_topics| {
97+
log.topics.len() >= expected_topics.len()
98+
&& expected_topics
99+
.iter()
100+
.zip(log.topics.iter())
101+
.all(|(expected, actual)| expected == actual)
102+
})
103+
} else {
104+
false
105+
}
106+
}
154107
}
155108
}

0 commit comments

Comments
 (0)