diff --git a/.env.example b/.env.example index 934677d..4eb2c46 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,7 @@ -## RPC_URL for connecting to an Ethereum node. -RPC_URL= -## SP1 prover network rpc URL -NETWORK_RPC_URL= -## Proof requester +NETWORK_RPC_URL=https://rpc.mainnet.succinct.xyz +RPC_URL=https://eth-sepolia.g.alchemy.com/v2/URjQnzNCUHumxPFL8VDoFBmpX4uqL6X8 PRIVATE_KEY= -## Interval (in seconds) at which to check for new transactions and create new rollup proofs. BLOCK_INTERVAL= -DATABASE_URL= - -DATA_STRATEGY=persist - +DATA_STRATEGY=delete \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 79cc0ec..617034b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -32,6 +32,6 @@ "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer", "editor.formatOnSave": true, - "editor.hover.enabled": true + "editor.hover.enabled": "on" }, } \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index d9ebd3a..6694298 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,12 @@ members = ["lib", "program", "contracts", "node"] default-members = ["node"] resolver = "2" +[profile.release] +lto = "thin" +codegen-units = 1 +panic = "abort" +strip = true + [workspace.dependencies] tokio = { version = "1", features = ["full"] } alloy-sol-types = "1.0.0" @@ -21,4 +27,4 @@ tiny-keccak = { git = "https://github.com/sp1-patches/tiny-keccak", tag = "patch curve25519-dalek = { git = "https://github.com/sp1-patches/curve25519-dalek", tag = "patch-4.1.3-sp1-5.0.0" } k256 = { git = "https://github.com/sp1-patches/elliptic-curves", tag = "patch-k256-13.4-sp1-5.0.0" } p256 = { git = "https://github.com/sp1-patches/elliptic-curves", tag = "patch-p256-13.2-sp1-5.0.0" } -ecdsa = { git = "https://github.com/sp1-patches/signatures", tag = "patch-16.9-sp1-4.1.0" } \ No newline at end of file +ecdsa = { git = "https://github.com/sp1-patches/signatures", tag = "patch-16.9-sp1-4.1.0" } diff --git a/Dockerfile b/Dockerfile index ddf8ae8..c170eef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,11 @@ -FROM rust:1.86 -WORKDIR /app +FROM rust:1.86 AS builder + +RUN apt-get update && apt-get install -y \ + clang mold \ + libpq-dev libssl-dev pkg-config libssl3 libpq5 \ + && rm -rf /var/lib/apt/lists/ -RUN apt-get update && apt-get install -y libpq-dev libssl-dev pkg-config curl libssl3 libpq5 ca-certificates && rm -rf /var/lib/apt/lists/ +WORKDIR /app COPY . . @@ -9,9 +13,12 @@ RUN curl -L https://sp1up.succinct.xyz | bash && \ export PATH="$HOME/.sp1/bin:$PATH" && \ sp1up -RUN cargo clean && \ - cargo build --release +RUN cargo clean && cargo build --release + +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install -y libpq-dev libpq5 ca-certificates && rm -rf /var/lib/apt/lists/* -RUN cargo install diesel_cli --no-default-features --features postgres +COPY --from=builder /app/target/release/node /usr/local/bin/prover-node -CMD ["sh", "-c", "diesel migration run --database-url $DATABASE_URL && /app/target/release/node"] +CMD ["prover-node"] \ No newline at end of file diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 84d1e7d..fd271f8 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -3,12 +3,6 @@ name = "energy-tracker-verifier" version = "0.1.0" edition = "2021" -default-run = "energy-tracker-verifier" - -[[bin]] -name = "energy-tracker-verifier" -path = "src/bin/main.rs" - [dependencies] alloy = { version = "1.0.13", features = ["full"] } alloy-rlp = { workspace = true } diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index 8126931..cf0123f 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -26,8 +26,8 @@ fn get_rollup_address() -> Address { .expect("Invalid address") } -fn get_m3ter_address() -> Address { - "0x40a36C0eF29A49D1B1c1fA45fab63762f8FC423F" +pub fn get_m3ter_address() -> Address { + "0x9C547B649475f1bE81323AefdbcF209C17961D5E" .parse() .expect("Invalid address") } diff --git a/docker-compose.yml b/docker-compose.yml index 93ceaa3..53dc283 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,7 +28,6 @@ services: ports: - 8080:8080 environment: - # - DB_PASSWORD=$(cat /run/secrets/db-password) - DATABASE_URL=${DATABASE_URL:-postgres://postgres:m3tering@db:5432/m3tering-db} depends_on: db: @@ -36,8 +35,18 @@ services: image: energy-tracker.public.dappnode.eth:0.1.0 restart: unless-stopped + streamr-client: + build: + context: ./streamr-client + ports: + - 3000:3000 + environment: + - DATABASE_URL=${DATABASE_URL:-postgres://postgres:m3tering@db:5432/m3tering-db} + depends_on: + db: + condition: service_healthy + image: streamr-client.public.dappnode.eth:0.1.0 + restart: unless-stopped + volumes: db-data: -# secrets: -# db-password: -# file: db/password.txt diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 0d0453e..964d332 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -4,7 +4,6 @@ use alloy_primitives::{Bytes, B256, U256}; use alloy_sol_types::sol; use alloy_trie::Nibbles; use ed25519_dalek::VerifyingKey; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; mod util; @@ -12,7 +11,7 @@ use util::validate_signature; pub use util::{ calc_slot_key, decode_slice, destructure_payload, extract_nonce, get_state_root, to_b256, - to_keccak_hash, to_u256, trim_zeros, verify_account_proof + to_keccak_hash, to_u256, trim_zeros, verify_account_proof, }; sol! { @@ -197,46 +196,33 @@ pub fn track_energy( } let verifying_key = util::build_verifying_key(&m3ter.public_key).unwrap(); + let mut nonce = start_nonce; + let mut energy_sum = 0u64; + for payload in m3ter_payloads { + println!("nonce {}, payload nonce {}", nonce, payload.nonce); + if !m3ter.validate_payload(payload, verifying_key) { + println!("Invalid payload: {:?}", payload); + break; + }; + if nonce + 1 != payload.nonce { + println!( + "Invalid nonce: {} not consercutive to {} for m3ter_id {}", + &nonce, &payload.nonce, &m3ter.m3ter_id + ); + nonce = if nonce < payload.nonce { + nonce + } else { + payload.nonce + }; + break; + } + nonce = payload.nonce; + energy_sum += payload.energy; + println!( + "State: energy {:?}, nonce {:?}", + payload.energy, payload.nonce + ); + } - // let mut energy_sum = 0; - // let mut latest_nonce = start_nonce; - m3ter_payloads - .par_iter() - .fold( - || (0, start_nonce), - |(energy, nonce), payload| { - println!("nonce {}, payload nonce {}", nonce, payload.nonce); - if !m3ter.validate_payload(payload, verifying_key) { - println!("Invalid payload: {:?}", payload); - return (energy, nonce); - }; - if nonce + 1 != payload.nonce { - println!( - "Invalid nonce: {} not consercutive to {} for m3ter_id {}", - &nonce, &payload.nonce, &m3ter.m3ter_id - ); - return ( - energy, - if nonce < payload.nonce { - payload.nonce - } else { - nonce - }, - ); - } - let energy_sum = energy + payload.energy; - println!( - "State: energy {:?}, nonce {:?}", - payload.energy, payload.nonce - ); - (energy_sum, payload.nonce) - }, - ) - .reduce( - || (0, 0), - |a, b| { - println!("Reducing: {:?} + {:?}", a.0, b.0); - if a.0 != 0 || b.0 != 0 { (a.0 + b.0, a.1.max(b.1)) } else { (0, start_nonce) } - }, - ) + (energy_sum, nonce) } diff --git a/migrations/.keep b/migrations/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/migrations/00000000000000_diesel_initial_setup/down.sql b/migrations/00000000000000_diesel_initial_setup/down.sql deleted file mode 100644 index a9f5260..0000000 --- a/migrations/00000000000000_diesel_initial_setup/down.sql +++ /dev/null @@ -1,6 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - -DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); -DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/migrations/00000000000000_diesel_initial_setup/up.sql b/migrations/00000000000000_diesel_initial_setup/up.sql deleted file mode 100644 index e504455..0000000 --- a/migrations/00000000000000_diesel_initial_setup/up.sql +++ /dev/null @@ -1,52 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - - - - --- Sets up a trigger for the given table to automatically set a column called --- `updated_at` whenever the row is modified (unless `updated_at` was included --- in the modified columns) --- --- # Example --- --- ```sql --- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); --- --- SELECT diesel_manage_updated_at('users'); --- ``` -CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ -BEGIN - EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s - FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ -BEGIN - IF ( - NEW IS DISTINCT FROM OLD AND - NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at - ) THEN - NEW.updated_at := current_timestamp; - END IF; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Main table for energy payloads -CREATE TABLE IF NOT EXISTS m3ter_payloads ( - id SERIAL PRIMARY KEY, - m3ter_id BIGINT NOT NULL, - message VARCHAR NOT NULL, - signature VARCHAR NOT NULL, - nonce BIGINT NOT NULL, - energy BIGINT NOT NULL, - is_verified BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - updated_at TIMESTAMP NOT NULL DEFAULT NOW() -); - --- Optionally, add the updated_at trigger to the table -SELECT diesel_manage_updated_at('m3ter_payloads'); diff --git a/node/src/main.rs b/node/src/main.rs index 8214000..80eec2a 100644 --- a/node/src/main.rs +++ b/node/src/main.rs @@ -6,7 +6,7 @@ use axum::{ extract::{Query, State}, http::StatusCode, response::Json, - routing::{get, post}, + routing::get, }; use diesel::{ PgConnection, RunQueryDsl, @@ -15,16 +15,15 @@ use diesel::{ sql_query, table, }; -use energy_tracker_lib::{ - Payload, ProofStruct, PublicValuesStruct, calc_slot_key, destructure_payload, extract_nonce, decode_slice -}; +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, }; use serde::{Deserialize, Serialize}; use serde_json::json; use sp1_sdk::{ - include_elf, network::FulfillmentStrategy, HashableKey, Prover, ProverClient, SP1ProofWithPublicValues, SP1Stdin, SP1VerifyingKey + HashableKey, Prover, ProverClient, SP1ProofWithPublicValues, SP1Stdin, SP1VerifyingKey, + include_elf, network::FulfillmentStrategy, }; use tokio::time::{self, Duration}; @@ -44,12 +43,6 @@ struct ProofFixture { proof: Bytes, } -#[derive(Serialize, Deserialize, Debug)] -struct M3terPayloadInbound { - m3ter_id: i64, - message: String, -} - type DbPool = r2d2::Pool>; #[derive(Queryable, QueryableByName, Insertable, Serialize, Debug)] @@ -110,7 +103,7 @@ async fn main() { "SELECT * FROM m3ter_payloads WHERE is_verified = FALSE - LIMIT 1000", + ", ) .load::(&mut conn) .expect("Failed to load payloads"); @@ -150,18 +143,21 @@ async fn main() { let app = Router::new() .route("/", get(root)) .route("/health", get(health)) - .route("/payload", post(payload_handler)) - .route("/batch-payloads", post(batch_payload_handler)) .route("/run_prover", get(run_prover_handler)) .route("/vkey", get(get_prover_vkey)) - .route("/update_verified_payloads", get(update_verified_payloads_handler)) + .route( + "/update_verified_payloads", + get(update_verified_payloads_handler), + ) .with_state(db_state); println!("Starting server on http://localhost:8080"); let listener = tokio::net::TcpListener::bind("127.0.0.1:8080") .await .unwrap(); - axum::serve::serve(listener, app).await.expect("server should start"); + axum::serve::serve(listener, app) + .await + .expect("server should start"); } fn establish_db_connection() -> DbPool { @@ -183,83 +179,6 @@ async fn health(State(db_state): State>) -> Json Json(json!({ "code": code, "success": code == 200 })) } -async fn payload_handler( - State(db_state): State>, - Json(payload): Json, -) -> (StatusCode, Json) { - println!("Received payload: {:?}", payload); - - let mut connection = db_state.get().unwrap(); - - let m3ter_id = payload.m3ter_id; - let (message, signature, nonce, energy) = destructure_payload(&payload.message); - - if !is_unique_nonce(&mut connection, m3ter_id, nonce as i64) { - return ( - StatusCode::BAD_REQUEST, - Json(json!({ "error": "Nonce already exists" })), - ); - } - - let new_payload = NewM3terPayload { - m3ter_id, - message: message.to_string(), - signature: signature.to_string(), - nonce: nonce as i64, - energy: energy as i64, - is_verified: false, - }; - println!("Inserting payload"); - let inserted: M3terPayload = diesel::insert_into(m3ter_payloads::table) - .values(&new_payload) - .get_result(&mut connection) - .expect("Failed to insert payload"); - - println!("Inserted payload: {:?}", inserted); - (StatusCode::OK, Json(json!({ "received": inserted }))) -} - -async fn batch_payload_handler( - State(db_state): State>, - Json(payloads): Json>, -) -> (StatusCode, Json) { - let mut connection = db_state.get().unwrap(); - let received_count = payloads.len(); - - let new_payloads = payloads - .into_iter() - .filter(|item| { - is_unique_nonce(&mut connection, item.m3ter_id, extract_nonce(&item.message)) - }) - .map(|payload| { - let m3ter_id = payload.m3ter_id; - let (message, signature, nonce, energy) = destructure_payload(&payload.message); - NewM3terPayload { - m3ter_id, - message: message.to_string(), - signature: signature.to_string(), - nonce: nonce as i64, - energy: energy as i64, - is_verified: false, - } - }) - .collect::>(); - - println!("Inserting payload"); - let inserted: Vec = diesel::insert_into(m3ter_payloads::table) - .values(&new_payloads) - .get_results(&mut connection) - .expect("Failed to insert payload"); - - println!("Inserted payload: {:?}", inserted); - ( - StatusCode::OK, - Json( - json!({ "inserted": inserted, "nonces_inserted": inserted.len(), "nonces_repeated": received_count - inserted.len() }), - ), - ) -} - async fn run_prover_handler( State(db_state): State>, Query(params): Query>, @@ -268,12 +187,12 @@ async fn run_prover_handler( .get("proof_type") .map(|s| { if s != "plonk" && s != "groth16" { - "groth16" + "groth16".to_string() } else { - s + s.clone() } }) - .unwrap_or("groth16"); + .unwrap_or("groth16".to_string()); let mut conn = match db_state.get() { Ok(state) => state, @@ -284,45 +203,66 @@ async fn run_prover_handler( ); } }; - let proving_payload = sql_query( - "SELECT * - FROM m3ter_payloads - WHERE is_verified = FALSE - LIMIT 100", - ) - .load::(&mut conn) - .expect("Failed to load payloads"); - - let mut grouped: HashMap> = HashMap::new(); - for payload in &proving_payload { - grouped - .entry(payload.m3ter_id.to_string()) - .or_default() - .push(energy_tracker_lib::M3terPayload::new( - payload.message.clone(), - payload.signature.clone(), - payload.nonce as u64, - payload.energy as u64, - )); - } - let (result, error) = run_prover(grouped, proof_type).await; + tokio::spawn(async move { + let proving_payload = match sql_query( + "SELECT * + FROM m3ter_payloads + WHERE is_verified = FALSE + ORDER BY m3ter_id ASC, nonce ASC + ", + ) + .load::(&mut conn) + { + Ok(p) => p, + Err(e) => { + eprintln!("Failed to load payloads: {:?}", e); + return; + } + }; - if let Some(err) = error { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(json!({ "error": true, "message": err })), - ); - } + if proving_payload.is_empty() { + println!("No payloads to process"); + return; + } + + let mut grouped: HashMap> = HashMap::new(); + for payload in &proving_payload { + grouped + .entry(payload.m3ter_id.to_string()) + .or_default() + .push(energy_tracker_lib::M3terPayload::new( + payload.message.clone(), + payload.signature.clone(), + payload.nonce as u64, + payload.energy as u64, + )); + } + + 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) = result.unwrap(); ( StatusCode::OK, Json(json!({ "code": 200, - "success": true, - "proof": proof_fixture, - "tx_hash": hash, + "message": "Prove generation started..." })), ) } @@ -335,7 +275,9 @@ async fn get_prover_vkey() -> Json { })) } -async fn update_verified_payloads_handler(State(db_state): State>) -> (StatusCode, Json) { +async fn update_verified_payloads_handler( + State(db_state): State>, +) -> (StatusCode, Json) { let mut connection = db_state.get().unwrap(); let provider = get_provider().await.expect("Failed to get provider"); let previous_nonces = get_previous_values(&provider, U256::from(1)).await.unwrap(); @@ -372,8 +314,23 @@ async fn run_prover( let (pk, vk) = prover_client.setup(ENERGY_TRACKER_ELF); let proof = match match proof_type { - "plonk" => prover_client.prove(&pk, &stdin).strategy(FulfillmentStrategy::Auction).plonk().run_async().await, - "groth16" => prover_client.prove(&pk, &stdin).strategy(FulfillmentStrategy::Auction).groth16().run_async().await, + "plonk" => { + prover_client + .prove(&pk, &stdin) + .strategy(FulfillmentStrategy::Auction) + .max_price_per_pgu(1u64) + .plonk() + .run_async() + .await + } + "groth16" => { + prover_client + .prove(&pk, &stdin) + .strategy(FulfillmentStrategy::Auction) + .groth16() + .run_async() + .await + } _ => panic!("Unsupported proof type: {}", proof_type), } { Ok(proof) => proof, @@ -409,8 +366,7 @@ async fn build_proving_payload( let m3ter_id: u64 = key.parse().expect("meter id not valid"); m3ter_id }) - .map(|m3ter_id| calc_slot_key(U256::from(m3ter_id)).unwrap()) - .map(|slot_key| B256::from_slice(&slot_key.to_be_bytes_vec())) + .map(|m3ter_id| to_b256(calc_slot_key(U256::from(m3ter_id)).unwrap())) .collect(); let (account_proof, encoded_account, storage_hash, proofs, anchor_block) = @@ -449,59 +405,38 @@ async fn update_payload( "delete" => DataStrategy::Delete, _ => DataStrategy::Persist, }; - nonces.chunks_exact(6) + nonces + .chunks_exact(6) .enumerate() .filter_map(|(i, nonce)| { let nonce = decode_slice(nonce.try_into().ok()?); - if nonce != 0 { Some((i, nonce as i64)) } else { + if nonce != 0 { + Some((i, nonce as i64)) + } else { None } }) - .for_each(|(i, nonce_)| { + .for_each(|(i, nonce_filter)| { use self::m3ter_payloads::dsl::*; use diesel::prelude::*; match strategy { DataStrategy::Persist => { - let _ = diesel::update(m3ter_payloads.filter(m3ter_id.eq(i as i64).and(nonce.le(nonce_)))) + let _ = diesel::update( + m3ter_payloads.filter(m3ter_id.eq(i as i64).and(nonce.le(nonce_filter))), + ) .set(is_verified.eq(true)) .execute(connection) .expect("Failed to update payloads"); - }, + } DataStrategy::Delete => { - let _ = diesel::delete(m3ter_payloads.filter(m3ter_id.eq(i as i64).and(nonce.eq(nonce_)))) + let _ = diesel::delete( + m3ter_payloads.filter(m3ter_id.eq(i as i64).and(nonce.eq(nonce_filter))), + ) .execute(connection) .expect("Failed to delete payloads"); } } }); - println!("Updated payloads as verified"); -} - - -fn is_unique_nonce( - connection: &mut PooledConnection>, - i_m3ter_id: i64, - i_nonce: i64, -) -> bool { - use self::m3ter_payloads::dsl::*; - use diesel::prelude::*; - - match m3ter_payloads - .filter(m3ter_id.eq(i_m3ter_id).and(nonce.eq(i_nonce))) - .first::(connection) - { - Ok(_) => { - println!( - "Nonce {} for m3ter {} already exists in the database", - i_nonce, i_m3ter_id - ); - false - } - Err(_) => { - println!("Nonce {} for m3ter {} is unique", i_nonce, i_m3ter_id); - true - } - } } fn create_proof_fixture(proof: &SP1ProofWithPublicValues, vk: &SP1VerifyingKey) -> ProofFixture { diff --git a/program/src/main.rs b/program/src/main.rs index 61f75c0..89998e9 100644 --- a/program/src/main.rs +++ b/program/src/main.rs @@ -10,7 +10,7 @@ use energy_tracker_lib::{ pub fn main() { let payload = sp1_zkvm::io::read::(); - let address = "40a36C0eF29A49D1B1c1fA45fab63762f8FC423F"; + let address = "9C547B649475f1bE81323AefdbcF209C17961D5E"; let mempool = payload.mempool; let initial_nonces = payload.previous_nonces; diff --git a/streamr-client/.dockerignore b/streamr-client/.dockerignore new file mode 100644 index 0000000..a4d5db6 --- /dev/null +++ b/streamr-client/.dockerignore @@ -0,0 +1,33 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/go/build-context-dockerignore/ + +**/.DS_Store +**/.classpath +**/.dockerignore + +**/.git +**/.github +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/charts +**/docker-compose* +**/compose.y*ml +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/secrets.dev.yaml +**/values.dev.yaml +/bin +/target +LICENSE +README.md diff --git a/streamr-client/.env.example b/streamr-client/.env.example new file mode 100644 index 0000000..f292e14 --- /dev/null +++ b/streamr-client/.env.example @@ -0,0 +1,4 @@ + +PRIVATE_KEY= +STREAM_ID=0x567853282663b601bfdb9203819b1fbb3fe18926/m3tering/test +STREAMR_ENV=live \ No newline at end of file diff --git a/streamr-client/Dockerfile b/streamr-client/Dockerfile new file mode 100644 index 0000000..608ddc7 --- /dev/null +++ b/streamr-client/Dockerfile @@ -0,0 +1,25 @@ +FROM node:20-alpine + +# Create app dir +WORKDIR /usr/src/app + +# Install build deps (git for private deps) and runtime deps +RUN apk add --no-cache cmake make g++ python3 py3-setuptools openssl-dev + +# Copy package files first to leverage layer caching +COPY package*.json ./ + +# Install dependencies +RUN npm install --only=production + +# Copy app sources +COPY . . + +# Optional: expose port if the client serves HTTP (adjust if different) +EXPOSE 3000 + +# Use non-root user provided by the image +USER node + +# Default command — adjust if your start script differs (e.g. "npm start") +CMD ["node", "index.js"] \ No newline at end of file diff --git a/streamr-client/db/migrations/initial_00 b/streamr-client/db/migrations/initial_00 index e08c029..c2f521a 100644 --- a/streamr-client/db/migrations/initial_00 +++ b/streamr-client/db/migrations/initial_00 @@ -4,6 +4,7 @@ CREATE TABLE IF NOT EXISTS m3ter_payloads ( m3ter_id BIGINT NOT NULL, nonce BIGINT NOT NULL, energy BIGINT NOT NULL, + message VARCHAR NOT NULL, signature VARCHAR NOT NULL, is_verified BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP diff --git a/streamr-client/db/save-gossip-message.js b/streamr-client/db/save-gossip-message.js index 3ea5a83..a6bb417 100644 --- a/streamr-client/db/save-gossip-message.js +++ b/streamr-client/db/save-gossip-message.js @@ -2,33 +2,33 @@ const pgp = require('pg-promise')() const decodePayload = require('../util/decode').decodePayload -async function saveGossipMessage(db, message) { - const cs = new pgp.helpers.ColumnSet(['m3ter_id', 'nonce', 'energy', 'signature', 'is_verified'], { table: 'm3ter_payloads' }); - - const existing = await db.manyOrNone( - 'SELECT m3ter_id, nonce FROM m3ter_payloads WHERE (m3ter_id, nonce) IN ($1:list)', - [message.map(msg => { - const { nonce } = decodePayload(msg.payload); - return `(${msg.m3terId},${nonce})`; - })] - ); - const existingSet = new Set( - existing.map(e => `${e.m3ter_id}-${e.nonce}`) +async function saveGossipMessage(db, messages) { + console.log("Received messages to save:", messages); + const cs = new pgp.helpers.ColumnSet([ + 'm3ter_id', 'nonce', 'energy', 'message', 'signature', 'is_verified'], + { table: 'm3ter_payloads' } ); - const values = message - .map(msg => { - const { nonce, energy, signature } = decodePayload(msg.payload); - const key = `${msg.m3terId}-${nonce}`; - return existingSet.has(key) ? null : { - m3ter_id: msg.m3terId, + + const mapped = await Promise.all(messages + .map(async msg => { + const { nonce, energy, message, signature } = decodePayload(msg.message); + const exist = await db.oneOrNone( + 'SELECT id FROM m3ter_payloads WHERE m3ter_id = $1 AND nonce = $2', + [msg.m3ter_id, nonce] + ); + if (exist) return null + + return { + m3ter_id: msg.m3ter_id, nonce, energy, + message, signature, is_verified: false }; - }) - .filter(Boolean); // Remove null values - + })) + const values = mapped.filter(Boolean) + if (values.length === 0) { console.log("No new messages to save"); return; diff --git a/streamr-client/index.js b/streamr-client/index.js index 447957b..d229837 100644 --- a/streamr-client/index.js +++ b/streamr-client/index.js @@ -1,5 +1,6 @@ const express = require('express') +const events = require('events') const dotenv = require('dotenv') dotenv.config() @@ -25,7 +26,9 @@ async function initializeDatabase() { return db } -initializeDatabase().then(db => { +initializeDatabase().then(async db => { + + events.setMaxListeners(20) const app = express() const port = process.env.PORT || 3000 @@ -33,13 +36,10 @@ initializeDatabase().then(db => { res.send('Hello World!') }) - streamr.subscribe(STREAM_ID, data => { - console.log("📥 Received message:", data) - - // Type guard to ensure data is a StreamrMessage + let sub = await streamr.subscribe(STREAM_ID, data => { if ( data && - typeof data === "array" + Array.isArray(data) ) { saveGossipMessage(db, data) } else { @@ -47,6 +47,23 @@ initializeDatabase().then(db => { } }) + console.log(`Subscribed to stream: ${sub.streamPartId}`) + + sub.on('error', (err) => { + console.error('Subscription error:', err) + }) + + // Clean up subscription on process exit + process.on('SIGINT', async () => { + try { + await sub.unsubscribe() + process.exit(0) + } catch (err) { + console.error('Error during cleanup:', err) + process.exit(1) + } + }) + app.listen(port, () => { console.log(`Server is running on port ${port}`) }) diff --git a/streamr-client/package-lock.json b/streamr-client/package-lock.json index af60802..2b8e290 100644 --- a/streamr-client/package-lock.json +++ b/streamr-client/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@streamr/sdk": "^103.1.0", + "dotenv": "^17.2.3", "express": "^5.1.0", "pg-promise": "^12.2.0" } @@ -166,47 +167,47 @@ } }, "node_modules/@streamr/autocertifier-client": { - "version": "103.1.0", - "resolved": "https://registry.npmjs.org/@streamr/autocertifier-client/-/autocertifier-client-103.1.0.tgz", - "integrity": "sha512-bbkodMFk49ePqrEJzI78qePqSJm8CZ/RNoi1QoTUILQzH/ghYQ3orzy1HIuGraJLIF4IJr08TT691CdIj+eeTg==", + "version": "103.1.2", + "resolved": "https://registry.npmjs.org/@streamr/autocertifier-client/-/autocertifier-client-103.1.2.tgz", + "integrity": "sha512-Hf50kGiDXJJOKx2/3e7pXya/OAB9U37ki+NKefeOa5vQQK8VaieOZ8Pu15j5O5dinMDOmymolmnp2lNUJj06fQ==", "license": "STREAMR NETWORK OPEN SOURCE LICENSE", "dependencies": { "@protobuf-ts/runtime-rpc": "^2.8.2", - "@streamr/utils": "103.1.0", + "@streamr/utils": "103.1.2", "eventemitter3": "^5.0.0", "node-forge": "^1.3.1" } }, "node_modules/@streamr/cdn-location": { - "version": "103.1.0", - "resolved": "https://registry.npmjs.org/@streamr/cdn-location/-/cdn-location-103.1.0.tgz", - "integrity": "sha512-sZUmPDhN7TjG4vVTCQhEywGWwN/IwmbbfM5DZh3ImMcPviyYalLzUO1iAEnyLcUFT4UbOYzjES5/0cxhrtn+CA==", + "version": "103.1.2", + "resolved": "https://registry.npmjs.org/@streamr/cdn-location/-/cdn-location-103.1.2.tgz", + "integrity": "sha512-L9y7cYYsJN0KQ1vr7w5+OUJx7h2RO0bkU0KgqSZviLuUw9PnZp4P7gyLsMEyJP1KlJwaAz7UEc4VCVn/DDTxmw==", "license": "Apache-2.0", "dependencies": { - "@streamr/utils": "103.1.0", + "@streamr/utils": "103.1.2", "haversine": "^1.1.1" } }, "node_modules/@streamr/config": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@streamr/config/-/config-5.9.0.tgz", - "integrity": "sha512-RbscU83EMjNnrkMFRA0e+bb6BVUBCicwCJQMfG2JZ7XrxLlopP374LnW95i1JrU1nWS+wfDpBxIP4lXd+hf/DQ==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/@streamr/config/-/config-5.9.2.tgz", + "integrity": "sha512-ZLt9kMHYqQjyZkRrQCNWLLzXc6+pON2ktf2bHTEjIE5occXsAo8JsWk3kMP3AiYzluy8lXDB/+F+t/397YsKvg==", "license": "STREAMR NETWORK OPEN SOURCE LICENSE" }, "node_modules/@streamr/dht": { - "version": "103.1.0", - "resolved": "https://registry.npmjs.org/@streamr/dht/-/dht-103.1.0.tgz", - "integrity": "sha512-eau7WJYksyqU4ccXI/QqvKVHPwMU0kvgBcRfNtOejRqraEXTZqBRnPMaKMgF8ZLvnx5zgrsw3YC07f1mQyzfXA==", + "version": "103.1.2", + "resolved": "https://registry.npmjs.org/@streamr/dht/-/dht-103.1.2.tgz", + "integrity": "sha512-d7ztpk1kfbK0ziHjfCXdqavAHOy77RaBAgnsTLvH4rDz4wV4tqLMC+hp/BieGtYlLxbgnFZivU4PD3ub7TL+Hg==", "license": "STREAMR NETWORK OPEN SOURCE LICENSE", "dependencies": { "@js-sdsl/ordered-map": "^4.4.2", "@protobuf-ts/runtime": "^2.8.2", "@protobuf-ts/runtime-rpc": "^2.8.2", - "@streamr/autocertifier-client": "103.1.0", - "@streamr/cdn-location": "103.1.0", - "@streamr/geoip-location": "103.1.0", - "@streamr/proto-rpc": "103.1.0", - "@streamr/utils": "103.1.0", + "@streamr/autocertifier-client": "103.1.2", + "@streamr/cdn-location": "103.1.2", + "@streamr/geoip-location": "103.1.2", + "@streamr/proto-rpc": "103.1.2", + "@streamr/utils": "103.1.2", "eventemitter3": "^5.0.0", "heap": "^0.2.6", "ipaddr.js": "^2.0.1", @@ -223,26 +224,17 @@ "utf-8-validate": "^6.0.5" } }, - "node_modules/@streamr/dht/node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/@streamr/geoip-location": { - "version": "103.1.0", - "resolved": "https://registry.npmjs.org/@streamr/geoip-location/-/geoip-location-103.1.0.tgz", - "integrity": "sha512-MjZIMjiXeYDpVIfR2U0Rx+Czs4ticBYGXHIZWX1+YYrgyI8840U3ipOnMk2ekjYUBfAHFm/t0WOKX9taegFGkQ==", + "version": "103.1.2", + "resolved": "https://registry.npmjs.org/@streamr/geoip-location/-/geoip-location-103.1.2.tgz", + "integrity": "sha512-3dLUCGYgO4E2UmVxOyMyLzjvWFjzCueTYmqBbYbjpAHKFpqPv9Aw3skADTo3WA/8yzq8BLGaBC1eNXZvX+fKLQ==", "license": "Apache-2.0", "dependencies": { - "@streamr/utils": "103.1.0", + "@streamr/utils": "103.1.2", "eventemitter3": "^5.0.0", "long-timeout": "^0.1.1", "mmdb-lib": "^3.0.1", - "tar": "^7.5.1", + "tar": "^7.5.2", "uuid": "^11.1.0" } }, @@ -253,14 +245,14 @@ "license": "STREAMR NETWORK OPEN SOURCE LICENSE" }, "node_modules/@streamr/proto-rpc": { - "version": "103.1.0", - "resolved": "https://registry.npmjs.org/@streamr/proto-rpc/-/proto-rpc-103.1.0.tgz", - "integrity": "sha512-nrKBiMwQIEiUW+/n8CH/TPvcj14uPfIsdZznu7COGGN6QsSN7W0SoFBzd0ql4ofLvvyWtxozx9L4/mbE2cQC5g==", + "version": "103.1.2", + "resolved": "https://registry.npmjs.org/@streamr/proto-rpc/-/proto-rpc-103.1.2.tgz", + "integrity": "sha512-SyStd/qctD7+5Y2IE76/BtYwoaG3aVY7qmdZnqZTTZSqyKBbSQfd8piCa6p4cd5PlRgnHGL5kCjTxJ+aIzuQTg==", "license": "(Apache-2.0 AND BSD-3-Clause)", "dependencies": { "@protobuf-ts/runtime": "^2.8.2", "@protobuf-ts/runtime-rpc": "^2.8.2", - "@streamr/utils": "103.1.0", + "@streamr/utils": "103.1.2", "eventemitter3": "^5.0.0", "lodash": "^4.17.21", "uuid": "^11.1.0" @@ -271,9 +263,9 @@ } }, "node_modules/@streamr/sdk": { - "version": "103.1.0", - "resolved": "https://registry.npmjs.org/@streamr/sdk/-/sdk-103.1.0.tgz", - "integrity": "sha512-6Lvx4gdliBrTlFwTlwZWQ0dnXnqbP+uNfjJi6g0sCIRoFgbGR0yfqw4X/dajTRA9t2r6MndK4K8smf3xVfraew==", + "version": "103.1.2", + "resolved": "https://registry.npmjs.org/@streamr/sdk/-/sdk-103.1.2.tgz", + "integrity": "sha512-CGN2U0Zt2c+NZ3FGNEtmL0s15BjwxlHVIsLz7+ki11eNbFcYksEjU5A3/OY+fjAWVPmZaqLnClV/7eBbFSP+sQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.28.4", @@ -281,13 +273,13 @@ "@noble/post-quantum": "^0.4.1", "@protobuf-ts/runtime": "^2.8.2", "@protobuf-ts/runtime-rpc": "^2.8.2", - "@streamr/config": "^5.9.0", - "@streamr/dht": "103.1.0", + "@streamr/config": "^5.9.2", + "@streamr/dht": "103.1.2", "@streamr/network-contracts": "^9.1.0", - "@streamr/proto-rpc": "103.1.0", - "@streamr/trackerless-network": "103.1.0", - "@streamr/utils": "103.1.0", - "core-js": "^3.45.1", + "@streamr/proto-rpc": "103.1.2", + "@streamr/trackerless-network": "103.1.2", + "@streamr/utils": "103.1.2", + "core-js": "^3.46.0", "env-paths": "^2.2.1", "ethers": "^6.13.0", "eventemitter3": "^5.0.0", @@ -304,7 +296,7 @@ "ts-toolbelt": "^9.6.0", "tsyringe": "^4.10.0", "uuid": "^11.1.0", - "zod": "^4.1.11" + "zod": "^4.1.12" }, "optionalDependencies": { "bufferutil": "^4.0.9", @@ -312,16 +304,16 @@ } }, "node_modules/@streamr/trackerless-network": { - "version": "103.1.0", - "resolved": "https://registry.npmjs.org/@streamr/trackerless-network/-/trackerless-network-103.1.0.tgz", - "integrity": "sha512-QprXDy8hPftvz65DYxY7WfqYDr5DGvwXCwo8WSWZCow0I83w123fPesB62EnU5J/s7FVPhjkRYDsI2Rbpav8lQ==", + "version": "103.1.2", + "resolved": "https://registry.npmjs.org/@streamr/trackerless-network/-/trackerless-network-103.1.2.tgz", + "integrity": "sha512-oTRsSWDjaZdkYvyiqAWyz69FDiOXdlJoV8YD8H/wrtHO4nYfYnhVhhInJNRGTU4NeYLzrmDs+VAx+eFyTGGFtQ==", "license": "STREAMR NETWORK OPEN SOURCE LICENSE", "dependencies": { "@protobuf-ts/runtime": "^2.8.2", "@protobuf-ts/runtime-rpc": "^2.8.2", - "@streamr/dht": "103.1.0", - "@streamr/proto-rpc": "103.1.0", - "@streamr/utils": "103.1.0", + "@streamr/dht": "103.1.2", + "@streamr/proto-rpc": "103.1.2", + "@streamr/utils": "103.1.2", "eventemitter3": "^5.0.0", "lodash": "^4.17.21", "ts-essentials": "^10.1.1", @@ -330,17 +322,17 @@ } }, "node_modules/@streamr/utils": { - "version": "103.1.0", - "resolved": "https://registry.npmjs.org/@streamr/utils/-/utils-103.1.0.tgz", - "integrity": "sha512-0/jVs9ThwpvjyauryUuBFmyVVSuPmThixYWXiuiVZlhSzCPVWqqhLIaZkt1J82H871eQYFa5QIlYHU0gs1pxyA==", + "version": "103.1.2", + "resolved": "https://registry.npmjs.org/@streamr/utils/-/utils-103.1.2.tgz", + "integrity": "sha512-m9U1EaxfuxAhIyqA59lCNOFd3fH2S8KoTgV9OopTZatMCMIwAgfe1XC1YJ8mLu/XhRSuAnlvjE2QDN4ukAichg==", "license": "Apache-2.0", "dependencies": { "@noble/curves": "^1.9.7", "@noble/post-quantum": "^0.4.1", "eventemitter3": "^5.0.0", "lodash": "^4.17.21", - "pino": "^9.12.0", - "pino-pretty": "^13.1.1", + "pino": "^10.1.0", + "pino-pretty": "^13.1.2", "secp256k1": "^5.0.1", "sha3": "^2.1.4" } @@ -558,23 +550,27 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/brace-expansion": { @@ -797,15 +793,16 @@ "optional": true }, "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -836,9 +833,9 @@ } }, "node_modules/core-js": { - "version": "3.46.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz", - "integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==", + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -847,9 +844,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.46.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.46.0.tgz", - "integrity": "sha512-NMCW30bHNofuhwLhYPt66OLOKTMbOhgTTatKVbaQC3KRHpTCiRIBYvtshr+NBYSnBxwAFhjW/RfJ0XbIjS16rw==", + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.47.0.tgz", + "integrity": "sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -945,6 +942,18 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1006,6 +1015,19 @@ "iconv-lite": "^0.6.2" } }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -1132,9 +1154,9 @@ } }, "node_modules/ethers": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.15.0.tgz", - "integrity": "sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.16.0.tgz", + "integrity": "sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==", "funding": [ { "type": "individual", @@ -1230,18 +1252,19 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -1281,9 +1304,9 @@ } }, "node_modules/fast-copy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", - "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.0.tgz", + "integrity": "sha512-/oA0gx1xyXE9R2YlV4FXwZJXngFdm9Du0zN8FhY38jnLkhp1u35h6bCyKgRhlsA6C9I+1vfXE4KISdt7xc6M9w==", "license": "MIT" }, "node_modules/fast-safe-stringify": { @@ -1299,9 +1322,9 @@ "license": "MIT" }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -1312,7 +1335,11 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/forwarded": { @@ -1550,28 +1577,23 @@ "optional": true }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-proxy-agent": { @@ -1614,15 +1636,19 @@ } }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/idb": { @@ -1703,9 +1729,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "license": "MIT", "optional": true, "engines": { @@ -1713,12 +1739,12 @@ } }, "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">= 10" } }, "node_modules/is-fullwidth-code-point": { @@ -1788,10 +1814,10 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "license": "ISC", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } @@ -1906,15 +1932,19 @@ } }, "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/mimic-fn": { @@ -2135,9 +2165,9 @@ "license": "ISC" }, "node_modules/node-abi": { - "version": "3.80.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.80.0.tgz", - "integrity": "sha512-LyPuZJcI9HVwzXK1GPxWNzrr+vr8Hp/3UqlmWxxh8p54U1ZbclOqbSog9lWHaCX+dBaiGi6n/hIX+mKu74GmPA==", + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -2166,9 +2196,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" @@ -2545,15 +2575,15 @@ } }, "node_modules/pg-promise": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-12.2.0.tgz", - "integrity": "sha512-th+GB7ftaRv5gAjSVslURSaYr5gf8d+T9/h5dZTJ/uyMqnQV8lJ8cDo3p5Crv3rprLC8ZCav9yLFcMKnobib+g==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-12.3.0.tgz", + "integrity": "sha512-uT3cPdn7huMgc1pfJwBey5aruKXTSyHtt/OxPdjUhVVEP/YftvFlSDkVnKopf1WTRyBSVV5ctv8pXKMBehvIpA==", "license": "MIT", "dependencies": { "assert-options": "0.8.3", "pg": "8.16.3", "pg-minify": "1.8.0", - "spex": "4.0.2" + "spex": "4.1.0" }, "engines": { "node": ">=16.0" @@ -2607,9 +2637,9 @@ } }, "node_modules/pino": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", - "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", + "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", "license": "MIT", "dependencies": { "@pinojs/redact": "^0.4.0", @@ -2638,20 +2668,20 @@ } }, "node_modules/pino-pretty": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.2.tgz", - "integrity": "sha512-3cN0tCakkT4f3zo9RXDIhy6GTvtYD6bK4CRBLN9j3E/ePqN1tugAXD5rGVfoChW6s0hiek+eyYlLNqc/BG7vBQ==", + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", + "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", "license": "MIT", "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", - "fast-copy": "^3.0.2", + "fast-copy": "^4.0.0", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", + "pino-abstract-transport": "^3.0.0", "pump": "^3.0.0", "secure-json-parse": "^4.0.0", "sonic-boom": "^4.0.1", @@ -2661,6 +2691,15 @@ "pino-pretty": "bin.js" } }, + "node_modules/pino-pretty/node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, "node_modules/pino-std-serializers": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", @@ -2782,6 +2821,15 @@ "node": ">= 0.10" } }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -2832,36 +2880,20 @@ } }, "node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.10" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -3270,12 +3302,12 @@ } }, "node_modules/spex": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spex/-/spex-4.0.2.tgz", - "integrity": "sha512-/8VnouFOkRlkfj/sDN6GYnRKCutBHjUndkg6oAgv374VvjYQRzzR2gTEXRsmFmgd1SrbI8W948iTDnkEkUieCw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/spex/-/spex-4.1.0.tgz", + "integrity": "sha512-ktgNAQ1X9x1A3IMChM6XBDeVjhGPbLgPQ8aEzGOaUIhZTnLeJSBApvi3gXT789hee6h73N3jOeWkXDwoPbYT/A==", "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/split2": { @@ -3825,9 +3857,9 @@ } }, "node_modules/zod": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", - "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/streamr-client/package.json b/streamr-client/package.json index 2d14737..84cb6a8 100644 --- a/streamr-client/package.json +++ b/streamr-client/package.json @@ -12,6 +12,7 @@ "dependencies": { "@streamr/sdk": "^103.1.0", "express": "^5.1.0", - "pg-promise": "^12.2.0" + "pg-promise": "^12.2.0", + "dotenv": "^17.2.3" } } diff --git a/streamr-client/util/decode.js b/streamr-client/util/decode.js index 3cca3b5..5d360dc 100644 --- a/streamr-client/util/decode.js +++ b/streamr-client/util/decode.js @@ -1,22 +1,26 @@ -function decodePayload(buf) { +function decodePayload(msg) { + const buf = Buffer.from(msg, 'hex'); if (buf.length < 72) { throw new Error("Payload too short. Must be at least 72 bytes"); } const nonce = buf.readUInt32BE(0); + const energy = buf.readUInt32BE(4); - const rawEnergy = buf.readUInt32BE(4); - const energyKWh = rawEnergy / 1e6; - + const message = buf.subarray(0, 8).toString("hex"); const signature = buf.subarray(8, 72).toString("hex"); return { nonce, - energy: energyKWh, + energy, + message, signature, }; } module.exports = { decodePayload, -}; \ No newline at end of file +}; + + +