From 065670eb5bec71a769fd514c61afe93b386310f4 Mon Sep 17 00:00:00 2001 From: Filipe Azevedo Date: Fri, 7 Feb 2025 20:55:29 +0000 Subject: [PATCH] feat: Add DIPS ipfs validation --- Cargo.lock | 340 ++++++++++++++++++++++++++++++++-- Cargo.toml | 3 +- crates/dips/Cargo.toml | 9 + crates/dips/src/ipfs.rs | 234 +++++++++++++++++++++++ crates/dips/src/lib.rs | 58 +++++- crates/dips/src/price.rs | 29 +++ crates/dips/src/server.rs | 9 +- crates/service/src/service.rs | 7 + 8 files changed, 663 insertions(+), 26 deletions(-) create mode 100644 crates/dips/src/ipfs.rs create mode 100644 crates/dips/src/price.rs diff --git a/Cargo.lock b/Cargo.lock index 223f821b0..a56301f80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -965,6 +965,12 @@ dependencies = [ "rand", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.5.2" @@ -1066,7 +1072,7 @@ dependencies = [ "Inflector", "async-graphql-parser", "darling", - "proc-macro-crate", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "strum", @@ -1510,12 +1516,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + [[package]] name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -1736,7 +1754,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" dependencies = [ "once_cell", - "proc-macro-crate", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "syn 2.0.90", @@ -1880,9 +1898,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" dependencies = [ "serde", ] @@ -2137,6 +2155,22 @@ dependencies = [ "memchr", ] +[[package]] +name = "common-multipart-rfc7578" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baee326bc603965b0f26583e1ecd7c111c41b49bd92a344897476a352798869" +dependencies = [ + "bytes", + "futures-core", + "futures-util", + "http 0.2.12", + "mime", + "mime_guess", + "rand", + "thiserror 1.0.69", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -2203,6 +2237,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cost-model" version = "0.1.0" @@ -2369,6 +2412,26 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "data-encoding-macro" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" +dependencies = [ + "data-encoding", + "syn 1.0.109", +] + [[package]] name = "deadpool" version = "0.10.0" @@ -2467,6 +2530,26 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -2598,7 +2681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2671,7 +2754,7 @@ dependencies = [ "atomic", "pear", "serde", - "toml", + "toml 0.8.19", "uncased", "version_check", ] @@ -3378,6 +3461,19 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-multipart-rfc7578" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb2cf73e96e9925f4bed948e763aa2901c2f1a3a5f713ee41917433ced6671" +dependencies = [ + "bytes", + "common-multipart-rfc7578", + "futures-core", + "http 0.2.12", + "hyper 0.14.31", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -3684,7 +3780,7 @@ dependencies = [ "serde_with", "tempfile", "thegraph-core", - "toml", + "toml 0.8.19", "tracing", "tracing-test", "url", @@ -3698,8 +3794,16 @@ dependencies = [ "anyhow", "async-trait", "base64 0.22.1", + "bytes", + "derivative", + "futures", + "http 0.2.12", + "ipfs-api-backend-hyper", + "ipfs-api-prelude", "prost", "prost-types", + "serde", + "serde_yaml", "thegraph-core", "thiserror 1.0.69", "tokio", @@ -3926,6 +4030,48 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ipfs-api-backend-hyper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9d131b408b4caafe1e7c00d410a09ad3eb7e3ab68690cf668e86904b2176b4" +dependencies = [ + "async-trait", + "base64 0.13.1", + "bytes", + "futures", + "http 0.2.12", + "hyper 0.14.31", + "hyper-multipart-rfc7578", + "ipfs-api-prelude", + "thiserror 1.0.69", +] + +[[package]] +name = "ipfs-api-prelude" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b74065805db266ba2c6edbd670b23c4714824a955628472b2e46cc9f3a869cb" +dependencies = [ + "async-trait", + "bytes", + "cfg-if", + "common-multipart-rfc7578", + "dirs", + "futures", + "http 0.2.12", + "multiaddr", + "multibase", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "tracing", + "walkdir", +] + [[package]] name = "ipnet" version = "2.10.1" @@ -4089,7 +4235,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06c01ae0007548e73412c08e2285ffe5d723195bf268bce67b1b77c3bb2a14d" dependencies = [ "heck 0.5.0", - "proc-macro-crate", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "syn 2.0.90", @@ -4216,6 +4362,16 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -4461,6 +4617,61 @@ dependencies = [ "version_check", ] +[[package]] +name = "multiaddr" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b36f567c7099511fa8612bbbb52dda2419ce0bdbacf31714e3a5ffdb766d3bd" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "log", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint", + "url", +] + +[[package]] +name = "multibase" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" +dependencies = [ + "base-x", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835d6ff01d610179fbce3de1694d007e500bf33a7f29689838941d6bf783ae40" +dependencies = [ + "core2", + "multihash-derive", + "unsigned-varint", +] + +[[package]] +name = "multihash-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure 0.12.6", +] + [[package]] name = "multimap" version = "0.10.0" @@ -4860,7 +5071,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8781a75c6205af67215f382092b6e0a4ff3734798523e69073d4bcd294ec767b" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "syn 2.0.90", @@ -5132,6 +5343,16 @@ dependencies = [ "uint", ] +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror 1.0.69", + "toml 0.5.11", +] + [[package]] name = "proc-macro-crate" version = "3.2.0" @@ -5141,6 +5362,30 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -5260,8 +5505,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" dependencies = [ "bytes", - "heck 0.4.1", - "itertools 0.10.5", + "heck 0.5.0", + "itertools 0.13.0", "log", "multimap", "once_cell", @@ -5281,7 +5526,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.90", @@ -5408,7 +5653,7 @@ dependencies = [ "once_cell", "socket2 0.5.8", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5535,6 +5780,17 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "regex" version = "1.11.1" @@ -5825,7 +6081,7 @@ checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a" dependencies = [ "cfg-if", "glob", - "proc-macro-crate", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "regex", @@ -5843,7 +6099,7 @@ checksum = "ef0053bbffce09062bee4bcc499b0fbe7a57b879f1efe088d6d8d4c7adcdef9b" dependencies = [ "cfg-if", "glob", - "proc-macro-crate", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "regex", @@ -6413,6 +6669,19 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.7.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.6" @@ -6943,6 +7212,18 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -7109,7 +7390,7 @@ dependencies = [ "fastrand", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7432,6 +7713,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml" version = "0.8.19" @@ -7858,6 +8148,18 @@ dependencies = [ "void", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "unsigned-varint" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" + [[package]] name = "untrusted" version = "0.9.0" @@ -8181,7 +8483,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -8560,7 +8862,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.90", - "synstructure", + "synstructure 0.13.1", ] [[package]] @@ -8602,7 +8904,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.90", - "synstructure", + "synstructure 0.13.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 71d412abc..7a45f72f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = [ "crates/attestation", "crates/config", "crates/dips", - "crates/indexer-receipt", + "crates/indexer-receipt", "crates/monitor", "crates/query", "crates/service", @@ -82,6 +82,7 @@ prost = "0.13.4" prost-types = "0.13.3" dipper-rpc = { git = "https://github.com/edgeandnode/dipper/", rev = "c8700e2", default-features = false } tonic-build = "0.12.3" +serde_yaml = "0.9.21" [patch.crates-io.tap_core] git = "https://github.com/semiotic-ai/timeline-aggregation-protocol" diff --git a/crates/dips/Cargo.toml b/crates/dips/Cargo.toml index 049fd23b1..4c4e9a3e5 100644 --- a/crates/dips/Cargo.toml +++ b/crates/dips/Cargo.toml @@ -15,6 +15,15 @@ prost-types.workspace = true uuid.workspace = true base64.workspace = true tokio.workspace = true +futures = "0.3" + +http = "0.2" +derivative = "2.2.0" +ipfs-api-backend-hyper = {version = "0.6.0", features = ["with-send-sync"] } +ipfs-api-prelude = {version = "0.6.0", features = ["with-send-sync"] } +bytes = "1.10.0" +serde_yaml.workspace = true +serde.workspace = true [build-dependencies] tonic-build = { workspace = true } diff --git a/crates/dips/src/ipfs.rs b/crates/dips/src/ipfs.rs new file mode 100644 index 000000000..111de93e0 --- /dev/null +++ b/crates/dips/src/ipfs.rs @@ -0,0 +1,234 @@ +// Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs. +// SPDX-License-Identifier: Apache-2.0 + +use std::{str::FromStr, sync::Arc}; + +use derivative::Derivative; +use futures::TryStreamExt; +use http::Uri; +use ipfs_api_prelude::{IpfsApi, TryFromUri}; +use serde::Deserialize; +use tonic::async_trait; + +use crate::DipsError; + +#[async_trait] +pub trait IpfsFetcher: Send + Sync + std::fmt::Debug { + async fn fetch(&self, file: &str) -> Result; +} + +#[async_trait] +impl IpfsFetcher for Arc { + async fn fetch(&self, file: &str) -> Result { + self.as_ref().fetch(file).await + } +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct IpfsClient { + #[derivative(Debug = "ignore")] + client: ipfs_api_backend_hyper::IpfsClient, +} + +impl IpfsClient { + pub fn new(url: &str) -> anyhow::Result { + Ok(Self { + client: ipfs_api_backend_hyper::IpfsClient::build_with_base_uri( + Uri::from_str(url).map_err(|err| anyhow::Error::from(err))?, + ), + }) + } +} + +#[async_trait] +impl IpfsFetcher for IpfsClient { + async fn fetch(&self, file: &str) -> Result { + let content = self + .client + .get(file.as_ref()) + .map_ok(|chunk| chunk.to_vec()) + .try_concat() + .await + .unwrap(); + + let manifest: GraphManifest = serde_yaml::from_slice(&content) + .map_err(|_| DipsError::InvalidSubgraphManifest(file.to_string()))?; + + Ok(manifest) + } +} + +#[derive(Default, Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DataSource { + network: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GraphManifest { + data_sources: Vec, +} + +impl GraphManifest { + pub fn network(&self) -> Option { + self.data_sources.first().map(|ds| ds.network.clone()) + } +} + +#[cfg(test)] +#[derive(Debug)] +pub struct TestIpfsClient { + manifest: GraphManifest, +} + +#[cfg(test)] +impl TestIpfsClient { + pub fn mainnet() -> Self { + Self { + manifest: GraphManifest { + data_sources: vec![DataSource { + network: "mainnet".to_string(), + }], + }, + } + } +} + +#[cfg(test)] +#[async_trait] +impl IpfsFetcher for TestIpfsClient { + async fn fetch(&self, _file: &str) -> Result { + Ok(self.manifest.clone()) + } +} + +#[cfg(test)] +mod test { + use crate::ipfs::{DataSource, GraphManifest}; + + #[test] + fn test_deserialize_manifest() { + let manifest: GraphManifest = serde_yaml::from_str(&MANIFEST).unwrap(); + assert_eq!( + manifest, + GraphManifest { + data_sources: vec![ + DataSource { + network: "scroll".to_string() + }, + DataSource { + network: "scroll".to_string() + } + ], + } + ) + } + + const MANIFEST: &'static str = " +dataSources: + - kind: ethereum/contract + mapping: + abis: + - file: + /: /ipfs/QmTU8eKx6pCgtff6Uvc7srAwR8BPiM3jTMBw9ahrXBjRzY + name: Factory + apiVersion: 0.0.6 + entities: [] + eventHandlers: + - event: >- + PoolCreated(indexed address,indexed address,indexed + uint24,int24,address) + handler: handlePoolCreated + file: + /: /ipfs/Qmbj3ituUaFRnTuahJ8yCG9GPiPqsRYq2T7umucZzPpLFn + kind: ethereum/events + language: wasm/assemblyscript + name: Factory + network: scroll + source: + abi: Factory + address: '0x46B3fDF7b5CDe91Ac049936bF0bDb12c5d22202e' + startBlock: 82522 + - kind: ethereum/contract + mapping: + abis: + - file: + /: /ipfs/QmaxxqQ7xzbGDPWu184uoq2g5sofazB9B9tEDrpPjmRZ8q + name: NonfungiblePositionManager + + apiVersion: 0.0.6 + entities: [] + eventHandlers: + - event: 'IncreaseLiquidity(indexed uint256,uint128,uint256,uint256)' + handler: handleIncreaseLiquidity + file: + /: /ipfs/QmcWrYawVufpST4u2Ed8Jz6jxFFaYXxERGwqstrpniY8C5 + kind: ethereum/events + language: wasm/assemblyscript + name: NonfungiblePositionManager + network: scroll + source: + abi: NonfungiblePositionManager + address: '0x0389879e0156033202C44BF784ac18fC02edeE4f' + startBlock: 82597 +features: + - nonFatalErrors +schema: + file: + /: /ipfs/QmSCM39NPLAjNQXsnkqq6H8z8KBi5YkfYyApPYLQbbC2kb +specVersion: 0.0.4 +templates: + - kind: ethereum/contract + mapping: + abis: + - file: + /: /ipfs/QmULRc8Ac1J6YFy11z7JRpyThb6f7nmL5mMTQvN7LKj2Vy + name: Pool + - file: + /: /ipfs/QmTU8eKx6pCgtff6Uvc7srAwR8BPiM3jTMBw9ahrXBjRzY + name: Factory + - file: + /: /ipfs/QmXuTbDkNrN27VydxbS2huvKRk62PMgUTdPDWkxcr2w7j2 + name: ERC20 + apiVersion: 0.0.6 + entities: [] + eventHandlers: + - event: 'Initialize(uint160,int24)' + handler: handleInitialize + - event: >- + Swap(indexed address,indexed + address,int256,int256,uint160,uint128,int24) + handler: handleSwap + - event: >- + Mint(address,indexed address,indexed int24,indexed + int24,uint128,uint256,uint256) + handler: handleMint + - event: >- + Burn(indexed address,indexed int24,indexed + int24,uint128,uint256,uint256) + handler: handleBurn + - event: >- + Flash(indexed address,indexed + address,uint256,uint256,uint256,uint256) + handler: handleFlash + - event: >- + Collect(indexed address,address,indexed int24,indexed + int24,uint128,uint128) + handler: handlePoolCollect + - event: 'CollectProtocol(indexed address,indexed address,uint128,uint128)' + handler: handleProtocolCollect + - event: 'SetFeeProtocol(uint8,uint8,uint8,uint8)' + handler: handleSetProtocolFee + file: + /: /ipfs/QmPtcuzBcWWBGXFKGdfUgqZLJov4c4Crt85ANbER2eHdCb + kind: ethereum/events + language: wasm/assemblyscript + name: Pool + network: scroll + source: + abi: Pool + + "; +} diff --git a/crates/dips/src/lib.rs b/crates/dips/src/lib.rs index ea00ef060..18a61012a 100644 --- a/crates/dips/src/lib.rs +++ b/crates/dips/src/lib.rs @@ -3,14 +3,18 @@ use std::{str::FromStr, sync::Arc}; +use ipfs::IpfsFetcher; +use price::PriceCalculator; use thegraph_core::alloy::{ core::primitives::Address, - primitives::{b256, ChainId, PrimitiveSignature as Signature, B256}, + primitives::{b256, ChainId, PrimitiveSignature as Signature, Uint, B256}, signers::SignerSync, sol, sol_types::{eip712_domain, Eip712Domain, SolStruct, SolValue}, }; +pub mod ipfs; +pub mod price; pub mod proto; pub mod server; pub mod store; @@ -131,6 +135,14 @@ pub enum DipsError { PayerNotAuthorised(Address), #[error("voucher payee {actual} does not match the expected address {expected}")] UnexpectedPayee { expected: Address, actual: Address }, + #[error("invalid subgraph id {0}")] + InvalidSubgraphManifest(String), + #[error("voucher for chain id {0}, subgraph manifest has network {1}")] + SubgraphChainIdMistmatch(String, String), + #[error("chainId {0} is not supported")] + UnsupportedChainId(String), + #[error("price per block is below configured price for chain {0}, minimum: {1}, offered: {2}")] + PricePerBlockTooLow(String, u64, String), // cancellation #[error("cancelled_by is expected to match the signer")] UnexpectedSigner, @@ -276,6 +288,8 @@ pub async fn validate_and_create_agreement( expected_payee: &Address, allowed_payers: impl AsRef<[Address]>, voucher: Vec, + price_calculator: &PriceCalculator, + ipfs_client: Arc, ) -> Result { let decoded_voucher = SignedIndexingAgreementVoucher::abi_decode(voucher.as_ref(), true) .map_err(|e| DipsError::AbiDecoding(e.to_string()))?; @@ -287,6 +301,35 @@ pub async fn validate_and_create_agreement( decoded_voucher.validate(domain, expected_payee, allowed_payers)?; + let manifest = ipfs_client.fetch(&metadata.subgraphDeploymentId).await?; + match manifest.network() { + Some(chain_id) if chain_id == metadata.chainId => {} + Some(chain_id) => { + return Err(DipsError::SubgraphChainIdMistmatch( + metadata.chainId, + chain_id, + )) + } + None => return Err(DipsError::UnsupportedChainId("".to_string())), + } + + let chain_id = manifest + .network() + .ok_or_else(|| DipsError::UnsupportedChainId("".to_string()))?; + + let offered_price = decoded_voucher.voucher.maxOngoingAmountPerEpoch; + match price_calculator.get_minimum_price(&chain_id) { + Some(price) if offered_price.lt(&Uint::from(price)) => { + return Err(DipsError::PricePerBlockTooLow( + chain_id, + price, + offered_price.to_string(), + )) + } + Some(_) => {} + None => return Err(DipsError::UnsupportedChainId(chain_id)), + } + store .create_agreement(decoded_voucher.clone(), metadata) .await?; @@ -339,8 +382,9 @@ mod test { pub use crate::store::{AgreementStore, InMemoryAgreementStore}; use crate::{ - dips_agreement_eip712_domain, dips_cancellation_eip712_domain, CancellationRequest, - DipsError, IndexingAgreementVoucher, SubgraphIndexingVoucherMetadata, + dips_agreement_eip712_domain, dips_cancellation_eip712_domain, ipfs::TestIpfsClient, + price::PriceCalculator, CancellationRequest, DipsError, IndexingAgreementVoucher, + SubgraphIndexingVoucherMetadata, }; #[tokio::test] @@ -355,7 +399,7 @@ mod test { basePricePerEpoch: U256::from(10000_u64), pricePerEntity: U256::from(100_u64), protocolNetwork: "eip155:42161".to_string(), - chainId: "eip155:1".to_string(), + chainId: "mainnet".to_string(), subgraphDeploymentId: deployment_id, }; @@ -386,6 +430,8 @@ mod test { &payee_addr, vec![payer_addr], abi_voucher, + &PriceCalculator::for_testing(), + Arc::new(TestIpfsClient::mainnet()), ) .await .unwrap(); @@ -544,7 +590,7 @@ mod test { basePricePerEpoch: U256::from(10000_u64), pricePerEntity: U256::from(100_u64), protocolNetwork: "eip155:42161".to_string(), - chainId: "eip155:1".to_string(), + chainId: "mainnet".to_string(), subgraphDeploymentId: deployment_id, }; @@ -575,6 +621,8 @@ mod test { &payee_addr, vec![payer_addr], signed_voucher.encode_vec(), + &PriceCalculator::for_testing(), + Arc::new(TestIpfsClient::mainnet()), ) .await?; diff --git a/crates/dips/src/price.rs b/crates/dips/src/price.rs new file mode 100644 index 000000000..e98026b78 --- /dev/null +++ b/crates/dips/src/price.rs @@ -0,0 +1,29 @@ +// Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs. +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; + +#[derive(Debug, Default)] +pub struct PriceCalculator { + prices_per_chain: HashMap, + default_price: Option, +} + +impl PriceCalculator { + #[cfg(test)] + pub fn for_testing() -> Self { + Self { + prices_per_chain: HashMap::default(), + default_price: Some(100), + } + } + + pub fn is_supported(&self, chain_id: &str) -> bool { + self.get_minimum_price(chain_id).is_some() + } + pub fn get_minimum_price(&self, chain_id: &str) -> Option { + let chain_price = self.prices_per_chain.get(chain_id).map(|p| *p); + + chain_price.or(self.default_price) + } +} diff --git a/crates/dips/src/server.rs b/crates/dips/src/server.rs index 0feb35c39..926829834 100644 --- a/crates/dips/src/server.rs +++ b/crates/dips/src/server.rs @@ -4,10 +4,13 @@ use std::sync::Arc; use async_trait::async_trait; -use thegraph_core::alloy::{dyn_abi::Eip712Domain, primitives::Address}; +use thegraph_core::alloy::primitives::Address; +use thegraph_core::alloy::sol_types::Eip712Domain; use tonic::{Request, Response, Status}; use crate::{ + ipfs::IpfsFetcher, + price::PriceCalculator, proto::indexer::graphprotocol::indexer::dips::{ dips_service_server::DipsService, CancelAgreementRequest, CancelAgreementResponse, ProposalResponse, SubmitAgreementProposalRequest, SubmitAgreementProposalResponse, @@ -22,6 +25,8 @@ pub struct DipsServer { pub expected_payee: Address, pub allowed_payers: Vec
, pub domain: Eip712Domain, + pub ipfs_fetcher: Arc, + pub price_calculator: PriceCalculator, } #[async_trait] @@ -50,6 +55,8 @@ impl DipsService for DipsServer { &self.expected_payee, &self.allowed_payers, signed_voucher, + &self.price_calculator, + self.ipfs_fetcher.clone(), ) .await .map_err(Into::::into)?; diff --git a/crates/service/src/service.rs b/crates/service/src/service.rs index fea102ecf..f74807aaa 100644 --- a/crates/service/src/service.rs +++ b/crates/service/src/service.rs @@ -8,6 +8,8 @@ use axum::{extract::Request, serve, ServiceExt}; use clap::Parser; use indexer_config::{Config, DipsConfig, GraphNodeConfig, SubgraphConfig}; use indexer_dips::{ + ipfs::{IpfsClient, IpfsFetcher}, + price::PriceCalculator, proto::indexer::graphprotocol::indexer::dips::dips_service_server::{ DipsService, DipsServiceServer, }, @@ -133,11 +135,16 @@ pub async fn run() -> anyhow::Result<()> { .parse() .expect("invalid dips host port"); + let ipfs_fetcher: Arc = + Arc::new(IpfsClient::new("https://api.thegraph.com/ipfs/").unwrap()); + let dips = DipsServer { agreement_store: Arc::new(PsqlAgreementStore { pool: database }), expected_payee: indexer_address, allowed_payers: allowed_payers.clone(), domain: domain_separator, + ipfs_fetcher, + price_calculator: PriceCalculator::default(), }; info!("starting dips grpc server on {}", addr);