Skip to content

Commit c193e64

Browse files
committed
fix: validate dips pricing
1 parent 0a11da4 commit c193e64

File tree

9 files changed

+175
-22
lines changed

9 files changed

+175
-22
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ members = [
44
"crates/attestation",
55
"crates/config",
66
"crates/dips",
7-
"crates/indexer-receipt",
7+
"crates/indexer-receipt",
88
"crates/monitor",
99
"crates/query",
1010
"crates/service",
@@ -82,6 +82,7 @@ tonic-build = "0.12.3"
8282
serde_yaml = "0.9.21"
8383
bon = "3.3"
8484
test-log = { version = "0.2.12", features = ["trace"] }
85+
graph-networks-registry = "0.6.1"
8586

8687
[patch.crates-io.tap_core]
8788
git = "https://github.com/semiotic-ai/timeline-aggregation-protocol"

crates/dips/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,23 @@ uuid.workspace = true
1818
tokio.workspace = true
1919
indexer-monitor = { path = "../monitor" }
2020
tracing.workspace = true
21+
graph-networks-registry.workspace = true
2122

2223
bytes = { version = "1.10.0", optional = true }
2324
derivative = "2.2.0"
2425

2526
futures = "0.3"
2627
http = "0.2"
2728
prost = { workspace = true, optional = true }
28-
ipfs-api-backend-hyper = { version = "0.6.0", features = ["with-send-sync", "with-hyper-tls"] }
29+
ipfs-api-backend-hyper = { version = "0.6.0", features = [
30+
"with-send-sync",
31+
"with-hyper-tls",
32+
] }
2933
serde_yaml.workspace = true
3034
serde.workspace = true
3135
sqlx = { workspace = true, optional = true }
3236
tonic = { workspace = true, optional = true }
37+
serde_json.workspace = true
3338

3439
[dev-dependencies]
3540
rand = "0.9.0"

crates/dips/src/lib.rs

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use std::{str::FromStr, sync::Arc};
66
use server::DipsServerContext;
77
use thegraph_core::alloy::{
88
core::primitives::Address,
9-
primitives::{b256, ChainId, PrimitiveSignature as Signature, Uint, B256},
9+
primitives::{
10+
b256, ruint::aliases::U256, ChainId, PrimitiveSignature as Signature, Uint, B256,
11+
},
1012
signers::SignerSync,
1113
sol,
1214
sol_types::{eip712_domain, Eip712Domain, SolStruct, SolValue},
@@ -18,6 +20,7 @@ pub mod ipfs;
1820
pub mod price;
1921
#[cfg(feature = "rpc")]
2022
pub mod proto;
23+
pub mod registry;
2124
#[cfg(feature = "rpc")]
2225
pub mod server;
2326
pub mod signers;
@@ -145,8 +148,12 @@ pub enum DipsError {
145148
InvalidSubgraphManifest(String),
146149
#[error("chainId {0} is not supported")]
147150
UnsupportedChainId(String),
148-
#[error("price per block is below configured price for chain {0}, minimum: {1}, offered: {2}")]
149-
PricePerBlockTooLow(String, u64, String),
151+
#[error("price per epoch is below configured price for chain {0}, minimum: {1}, offered: {2}")]
152+
PricePerEpochTooLow(String, u64, String),
153+
#[error(
154+
"price per entity is below configured price for chain {0}, minimum: {1}, offered: {2}"
155+
)]
156+
PricePerEntityTooLow(String, u64, String),
150157
// cancellation
151158
#[error("cancelled_by is expected to match the signer")]
152159
UnexpectedSigner,
@@ -304,6 +311,7 @@ pub async fn validate_and_create_agreement(
304311
ipfs_fetcher,
305312
price_calculator,
306313
signer_validator,
314+
registry,
307315
} = ctx.as_ref();
308316
let decoded_voucher = SignedIndexingAgreementVoucher::abi_decode(voucher.as_ref(), true)
309317
.map_err(|e| DipsError::AbiDecoding(e.to_string()))?;
@@ -330,19 +338,33 @@ pub async fn validate_and_create_agreement(
330338
}
331339
}
332340

333-
let offered_price = metadata.pricePerEntity;
341+
let network = match registry.get_network_by_id(&metadata.chainId) {
342+
Some(network) => network.id.clone(),
343+
None => return Err(DipsError::UnsupportedChainId(metadata.chainId)),
344+
};
345+
346+
let offered_epoch_price = metadata.basePricePerEpoch;
334347
match price_calculator.get_minimum_price(&metadata.chainId) {
335-
Some(price) if offered_price.lt(&Uint::from(price)) => {
336-
return Err(DipsError::PricePerBlockTooLow(
337-
metadata.chainId,
348+
Some(price) if offered_epoch_price.lt(&Uint::from(price)) => {
349+
return Err(DipsError::PricePerEpochTooLow(
350+
network,
338351
price,
339-
offered_price.to_string(),
352+
offered_epoch_price.to_string(),
340353
))
341354
}
342355
Some(_) => {}
343356
None => return Err(DipsError::UnsupportedChainId(metadata.chainId)),
344357
}
345358

359+
let offered_entity_price = metadata.pricePerEntity;
360+
if offered_entity_price < U256::from(price_calculator.entity_price()) {
361+
return Err(DipsError::PricePerEntityTooLow(
362+
network,
363+
price_calculator.entity_price(),
364+
offered_entity_price.to_string(),
365+
));
366+
}
367+
346368
store
347369
.create_agreement(decoded_voucher.clone(), metadata)
348370
.await?;
@@ -754,7 +776,17 @@ mod test {
754776
subgraphDeploymentId: voucher_ctx.deployment_id.clone(),
755777
};
756778

757-
let low_price_voucher = voucher_ctx.test_voucher(metadata);
779+
let low_entity_price_voucher = voucher_ctx.test_voucher(metadata);
780+
781+
let metadata = SubgraphIndexingVoucherMetadata {
782+
basePricePerEpoch: U256::from(10_u64),
783+
pricePerEntity: U256::from(10000_u64),
784+
protocolNetwork: "eip155:42161".to_string(),
785+
chainId: "mainnet".to_string(),
786+
subgraphDeploymentId: voucher_ctx.deployment_id.clone(),
787+
};
788+
789+
let low_epoch_price_voucher = voucher_ctx.test_voucher(metadata);
758790

759791
let metadata = SubgraphIndexingVoucherMetadata {
760792
basePricePerEpoch: U256::from(10000_u64),
@@ -775,11 +807,16 @@ mod test {
775807
Err(DipsError::InvalidSubgraphManifest(
776808
voucher_ctx.deployment_id.clone(),
777809
)),
778-
Err(DipsError::PricePerBlockTooLow(
810+
Err(DipsError::PricePerEntityTooLow(
779811
"mainnet".to_string(),
780812
100,
781813
"10".to_string(),
782814
)),
815+
Err(DipsError::PricePerEpochTooLow(
816+
"mainnet".to_string(),
817+
200,
818+
"10".to_string(),
819+
)),
783820
Err(DipsError::SignerNotAuthorised(signer.address())),
784821
Ok(valid_voucher
785822
.voucher
@@ -790,7 +827,8 @@ mod test {
790827
];
791828
let cases = vec![
792829
no_network_voucher,
793-
low_price_voucher,
830+
low_entity_price_voucher,
831+
low_epoch_price_voucher,
794832
valid_voucher_invalid_signer,
795833
valid_voucher,
796834
];

crates/dips/src/price.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,27 @@ use std::collections::HashMap;
55

66
#[derive(Debug, Default)]
77
pub struct PriceCalculator {
8-
prices_per_chain: HashMap<String, u64>,
9-
default_price: Option<u64>,
8+
base_price_per_epoch: HashMap<String, u64>,
9+
price_per_entity: u64,
1010
}
1111

1212
impl PriceCalculator {
1313
#[cfg(test)]
1414
pub fn for_testing() -> Self {
1515
Self {
16-
prices_per_chain: HashMap::default(),
17-
default_price: Some(100),
16+
base_price_per_epoch: HashMap::from_iter(vec![("mainnet".to_string(), 200)]),
17+
price_per_entity: 100,
1818
}
1919
}
2020

2121
pub fn is_supported(&self, chain_id: &str) -> bool {
2222
self.get_minimum_price(chain_id).is_some()
2323
}
2424
pub fn get_minimum_price(&self, chain_id: &str) -> Option<u64> {
25-
let chain_price = self.prices_per_chain.get(chain_id).copied();
25+
self.base_price_per_epoch.get(chain_id).copied()
26+
}
2627

27-
chain_price.or(self.default_price)
28+
pub fn entity_price(&self) -> u64 {
29+
self.price_per_entity
2830
}
2931
}

crates/dips/src/registry.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#[cfg(test)]
5+
use graph_networks_registry::NetworksRegistry;
6+
7+
#[cfg(test)]
8+
pub fn test_registry() -> NetworksRegistry {
9+
use graph_networks_registry::{Network, NetworkType, Services};
10+
11+
NetworksRegistry {
12+
schema: "".to_string(),
13+
description: "".to_string(),
14+
networks: vec![
15+
Network {
16+
aliases: None,
17+
api_urls: None,
18+
caip2_id: "eip155:1".to_string(),
19+
docs_url: None,
20+
explorer_urls: None,
21+
firehose: None,
22+
full_name: "".to_string(),
23+
genesis: None,
24+
graph_node: None,
25+
icon: None,
26+
id: "mainnet".to_string(),
27+
indexer_docs_urls: None,
28+
issuance_rewards: false,
29+
native_token: None,
30+
network_type: NetworkType::Mainnet,
31+
relations: None,
32+
rpc_urls: None,
33+
second_name: None,
34+
services: Services {
35+
firehose: None,
36+
sps: None,
37+
subgraphs: None,
38+
substreams: None,
39+
},
40+
short_name: "mainnet".to_string(),
41+
},
42+
Network {
43+
aliases: None,
44+
api_urls: None,
45+
caip2_id: "eip155:1337".to_string(),
46+
docs_url: None,
47+
explorer_urls: None,
48+
firehose: None,
49+
full_name: "".to_string(),
50+
genesis: None,
51+
graph_node: None,
52+
icon: None,
53+
id: "hardhat".to_string(),
54+
indexer_docs_urls: None,
55+
issuance_rewards: false,
56+
native_token: None,
57+
network_type: NetworkType::Mainnet,
58+
relations: None,
59+
rpc_urls: None,
60+
second_name: None,
61+
services: Services {
62+
firehose: None,
63+
sps: None,
64+
subgraphs: None,
65+
substreams: None,
66+
},
67+
short_name: "hardhat".to_string(),
68+
},
69+
],
70+
title: "".to_string(),
71+
updated_at: "".to_string(),
72+
version: "".to_string(),
73+
}
74+
}

0 commit comments

Comments
 (0)