diff --git a/.dockerignore b/.dockerignore index a4d5db6..94be448 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,7 +7,7 @@ **/.DS_Store **/.classpath **/.dockerignore - +**/.env **/.git **/.github **/.gitignore diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml new file mode 100644 index 0000000..de78d20 --- /dev/null +++ b/.github/workflows/build-images.yml @@ -0,0 +1,89 @@ +name: Build and publish Docker images for Energy Tracker + +on: + push: + branches: [ "staging", "main" ] + pull_request: + branches: [ "staging", "main" ] + +env: + REGISTRY: ghcr.io + PROVER_IMAGE_NAME: ${{ github.repository }} + STREAMR_IMAGE_NAME: ${{ github.repository }}/streamr-client + +jobs: + build-and-push-images: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + attestations: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Build and push Prover image (energy-tracker) + - name: Extract metadata for Prover image + id: meta-prover + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.PROVER_IMAGE_NAME }} + + - name: Build and push Prover image + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta-prover.outputs.tags }} + labels: ${{ steps.meta-prover.outputs.labels }} + + - name: Generate attestation for Prover image + if: github.event_name != 'pull_request' + uses: actions/attest-build-provenance@v3 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.PROVER_IMAGE_NAME }} + subject-digest: ${{ steps.build-and-push-prover.outputs.digest }} + push-to-registry: true + + # Build and push Streamr client image + - name: Extract metadata for Streamr client image + id: meta-streamr + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.STREAMR_IMAGE_NAME }} + + - name: Build and push Streamr client image + uses: docker/build-push-action@v6 + with: + context: ./streamr-client + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta-streamr.outputs.tags }} + labels: ${{ steps.meta-streamr.outputs.labels }} + + - name: Generate attestation for Streamr client image + if: github.event_name != 'pull_request' + uses: actions/attest-build-provenance@v3 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.STREAMR_IMAGE_NAME }} + subject-digest: ${{ steps.build-and-push-streamr.outputs.digest }} + push-to-registry: true diff --git a/.github/workflows/docker-image-prover.yml b/.github/workflows/docker-image-prover.yml deleted file mode 100644 index 20e5f2b..0000000 --- a/.github/workflows/docker-image-prover.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Create and publish Prover Docker image - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - build-and-push-image: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - attestations: write - id-token: write - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - name: Log in to the Container registry - if: github.event_name != 'pull_request' - uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - name: Build and push Docker image - id: push - uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 - with: - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Generate artifact attestation - if: github.event_name != 'pull_request' - uses: actions/attest-build-provenance@v3 - with: - subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} - subject-digest: ${{ steps.push.outputs.digest }} - push-to-registry: true diff --git a/.github/workflows/docker-image-streamr-client.yml b/.github/workflows/docker-image-streamr-client.yml deleted file mode 100644 index 914a3ba..0000000 --- a/.github/workflows/docker-image-streamr-client.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Create and publish Prover streamr client image - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }}/streamr-client - -jobs: - build-and-push-image: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - attestations: write - id-token: write - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - name: Log in to the Container registry - if: github.event_name != 'pull_request' - uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - name: Build and push Docker image - id: push - uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 - with: - context: ./streamr-client - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Generate artifact attestation - if: github.event_name != 'pull_request' - uses: actions/attest-build-provenance@v3 - with: - subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} - subject-digest: ${{ steps.push.outputs.digest }} - push-to-registry: true diff --git a/Cargo.lock b/Cargo.lock index f9805e5..0176071 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4088,6 +4088,7 @@ dependencies = [ "dotenvy", "energy-tracker-lib", "energy-tracker-verifier", + "eyre", "serde", "serde_json", "sp1-build", diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index cf0123f..a7358d4 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -99,6 +99,19 @@ fn get_rollup_abi() -> JsonAbi { "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs":[], + "name":"SP1_PROGRAM_VKEY", + "outputs":[ + { + "internalType":"bytes32", + "name":"", + "type":"bytes32" + } + ], + "stateMutability":"view", + "type":"function" } ]"#; @@ -253,3 +266,21 @@ pub async fn commit_state( println!("Transaction confirmed in block: {:?}", receipt.block_number); Ok(hash) } + +pub async fn check_program_vkey(provider: &impl Provider, vkey_hash: [u8; 32]) -> Result { + let rollup_address = get_rollup_address(); + let abi: JsonAbi = get_rollup_abi(); + let interface = Interface::new(abi); + let contract = interface.connect(rollup_address, provider); + + let call_builder = contract.function( + "SP1_PROGRAM_VKEY", + &[], + )?; + + let result = call_builder.call().await?; + + let vkey = result[0].as_fixed_bytes().unwrap(); + println!("on-chain vkey: {:?}, current vkey {:?}", vkey.0, vkey_hash); + Ok(vkey.0 == vkey_hash.as_slice()) +} diff --git a/docker-compose.yml b/docker-compose.yml index 4aa903c..cb2df0c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,7 +37,7 @@ services: environment: - DATABASE_URL=${DATABASE_URL:-postgres://postgres:m3tering@db:5432/m3tering-db} - STREAM_ID=${STREAM_ID} - - PRIVATE_KEY=${STREAMR_PRIVATE_KEY} + - PRIVATE_KEY=${PRIVATE_KEY} - STREAMR_ENV=${STREAMR_ENV:-live} depends_on: db: diff --git a/node/Cargo.toml b/node/Cargo.toml index aea5a91..5b25983 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] sp1-sdk = { version = "5.2.1", default-features = false, features = ["network"] } axum = "0.8.4" +eyre = "0.6.8" diesel = { version = "2", features = ["postgres", "r2d2", "serde_json"] } serde = { workspace = true } alloy-sol-types = { workspace = true } diff --git a/node/src/main.rs b/node/src/main.rs index a534b6a..16211b4 100644 --- a/node/src/main.rs +++ b/node/src/main.rs @@ -15,10 +15,14 @@ use diesel::{ sql_query, table, }; -use energy_tracker_lib::{Payload, ProofStruct, PublicValuesStruct, calc_slot_key, decode_slice, to_b256}; +use energy_tracker_lib::{ + Payload, ProofStruct, PublicValuesStruct, calc_slot_key, decode_slice, to_b256, +}; use energy_tracker_verifier::{ - commit_state, get_block_rpl_bytes, get_previous_values, get_provider, get_storage_proofs, + check_program_vkey, commit_state, get_block_rpl_bytes, get_previous_values, get_provider, + get_storage_proofs, }; +use eyre::Result; use serde::{Deserialize, Serialize}; use serde_json::json; use sp1_sdk::{ @@ -106,7 +110,7 @@ async fn main() { ", ) .load::(&mut conn) - .expect("Failed to load payloads"); + .expect("Failed to load payloads"); if proving_payload.is_empty() { println!("No new payloads to process"); continue; @@ -123,18 +127,21 @@ async fn main() { payload.energy as u64, )); } - let (result, error) = run_prover(grouped, "groth16").await; - - if let Some((proof_fixture, hash)) = result { - println!("Committed state with tx hash: {}", hash); - update_payload(&mut conn, proof_fixture.new_nonces.to_vec()).await; - println!("Updated payloads as verified"); - } else { - println!("running prove failed. error {:?}", error.unwrap()) - } - } + + let (proof_fixture, hash) = match run_prover(grouped, "groth16").await { + Ok(res) => res, + Err(e) => { + eprintln!("Prover error: {}", e); + return; + } + }; + println!("Committed state with tx hash: {}", hash); + update_payload(&mut conn, proof_fixture.new_nonces.to_vec()).await; + println!("Updated payloads as verified"); + }, Err(e) => { - println!("encountered error {:?}", e); + eprintln!("Failed to get DB connection: {:?}", e); + break; } } } @@ -204,7 +211,7 @@ async fn run_prover_handler( } }; - tokio::spawn(async move { + tokio::spawn(async move { let proving_payload = match sql_query( "SELECT * FROM m3ter_payloads @@ -239,23 +246,16 @@ async fn run_prover_handler( )); } - let (result, error) = run_prover(grouped, &proof_type).await; - - if let Some(err) = error { - eprintln!("Prover error: {}", err); - return; - } - - match result { - Some((proof_fixture, hash)) => { - println!("Committed state with tx hash: {}", hash); - update_payload(&mut conn, proof_fixture.new_nonces.to_vec()).await; - println!("Updated payloads as verified"); - } - None => { - eprintln!("Failed to generate proof"); + let (proof_fixture, hash) = match run_prover(grouped, &proof_type).await { + Ok(res) => res, + Err(e) => { + eprintln!("Prover error: {}", e); + return; } - } + }; + println!("Committed state with tx hash: {}", hash); + update_payload(&mut conn, proof_fixture.new_nonces.to_vec()).await; + println!("Updated payloads as verified"); }); ( @@ -295,24 +295,26 @@ async fn update_verified_payloads_handler( async fn run_prover( payload: HashMap>, proof_type: &str, -) -> (Option<(ProofFixture, String)>, Option) { - let (payload, _) = build_proving_payload(payload).await; - +) -> Result<(ProofFixture, String)> { sp1_sdk::utils::setup_logger(); - - let mut stdin = SP1Stdin::new(); let private_key = env::var("PRIVATE_KEY").expect("PRIVATE_KEY not set in .env"); let rpc_url = env::var("NETWORK_RPC_URL").expect("RPC_URL not set in .env"); - stdin.write(&payload); let prover_client = ProverClient::builder() .network() .private_key(&private_key) .rpc_url(&rpc_url) - // .cpu() - // .cuda() .build(); let (pk, vk) = prover_client.setup(ENERGY_TRACKER_ELF); + + let (payload, _) = match build_proving_payload(payload, &vk).await { + Ok(res) => res, + Err(e) => return Err(eyre::eyre!("Failed to build payload: {:?}", e)), + }; + + let mut stdin = SP1Stdin::new(); + stdin.write(&payload); + let proof = match match proof_type { "plonk" => { prover_client @@ -334,7 +336,7 @@ async fn run_prover( _ => panic!("Unsupported proof type: {}", proof_type), } { Ok(proof) => proof, - Err(e) => return (None, Some(format!("Prover error: {:?}", e))), + Err(e) => return Err(eyre::eyre!("Prover error: {:?}", e)), }; let proof_fixture = create_proof_fixture(&proof, &vk); @@ -350,13 +352,24 @@ async fn run_prover( .await .expect("msg: Failed to commit state"); - (Some((proof_fixture, hash.to_string())), None) + Ok((proof_fixture, hash.to_string())) } async fn build_proving_payload( payload: HashMap>, -) -> (Payload, B256) { + vk: &SP1VerifyingKey, +) -> Result<(Payload, B256)> { let provider = get_provider().await.expect("Failed to get provider"); + + if !check_program_vkey(&provider, vk.bytes32_raw()) + .await + .unwrap() + { + return Err(eyre::eyre!( + "Program Vkey does not match the on-chain value" + )); + } + let previous_nonces = get_previous_values(&provider, U256::from(1)).await.unwrap(); let previous_balances = get_previous_values(&provider, U256::from(0)).await.unwrap(); @@ -375,7 +388,7 @@ async fn build_proving_payload( println!("Loaded payloads: {:?}", payload); println!("Anchor Block: {:?}", anchor_block); - ( + Ok(( Payload { mempool: payload, previous_nonces: previous_nonces.into(), @@ -389,7 +402,7 @@ async fn build_proving_payload( block_bytes: Some(block_bytes), }, anchor_block, - ) + )) } async fn update_payload(