Skip to content

Commit 721d0bc

Browse files
committed
feat: Escrow based signer validation
1 parent fed7619 commit 721d0bc

File tree

6 files changed

+191
-51
lines changed

6 files changed

+191
-51
lines changed

Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/dips/Cargo.toml

+8-6
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,28 @@ thiserror.workspace = true
1010
anyhow.workspace = true
1111
alloy-rlp = "0.3.10"
1212
thegraph-core.workspace = true
13-
tonic.workspace = true
14-
async-trait.workspace = true
15-
prost.workspace = true
16-
prost-types.workspace = true
13+
tonic.workspace = true
14+
async-trait.workspace = true
15+
prost.workspace = true
16+
prost-types.workspace = true
1717
uuid.workspace = true
1818
base64.workspace = true
1919
tokio.workspace = true
2020
sqlx.workspace = true
2121
futures = "0.3"
22+
indexer-monitor = { path = "../monitor" }
2223

2324
http = "0.2"
2425
derivative = "2.2.0"
25-
ipfs-api-backend-hyper = {version = "0.6.0", features = ["with-send-sync"] }
26-
ipfs-api-prelude = {version = "0.6.0", features = ["with-send-sync"] }
26+
ipfs-api-backend-hyper = { version = "0.6.0", features = ["with-send-sync"] }
27+
ipfs-api-prelude = { version = "0.6.0", features = ["with-send-sync"] }
2728
bytes = "1.10.0"
2829
serde_yaml.workspace = true
2930
serde.workspace = true
3031

3132
[dev-dependencies]
3233
rand = "0.9.0"
34+
indexer-watcher = { path = "../watcher" }
3335

3436
[build-dependencies]
3537
tonic-build = { workspace = true }

crates/dips/src/lib.rs

+43-27
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33

44
use std::{str::FromStr, sync::Arc};
55

6-
use ipfs::IpfsFetcher;
7-
use price::PriceCalculator;
6+
use server::DipsServerContext;
87
use thegraph_core::alloy::{
98
core::primitives::Address,
109
primitives::{b256, ChainId, PrimitiveSignature as Signature, Uint, B256},
@@ -18,6 +17,7 @@ pub mod ipfs;
1817
pub mod price;
1918
pub mod proto;
2019
pub mod server;
20+
pub mod signers;
2121
pub mod store;
2222

2323
use store::AgreementStore;
@@ -190,14 +190,16 @@ impl SignedIndexingAgreementVoucher {
190190
// TODO: Validate all values
191191
pub fn validate(
192192
&self,
193+
signer_validator: &Arc<dyn signers::SignerValidator>,
193194
domain: &Eip712Domain,
194195
expected_payee: &Address,
195196
allowed_payers: impl AsRef<[Address]>,
196197
) -> Result<(), DipsError> {
197198
let sig = Signature::from_str(&self.signature.to_string())
198199
.map_err(|err| DipsError::InvalidSignature(err.to_string()))?;
199200

200-
let payer = sig
201+
let payer = self.voucher.payer;
202+
let signer = sig
201203
.recover_address_from_prehash(&self.voucher.eip712_signing_hash(domain))
202204
.map_err(|err| DipsError::InvalidSignature(err.to_string()))?;
203205

@@ -207,6 +209,10 @@ impl SignedIndexingAgreementVoucher {
207209
return Err(DipsError::PayerNotAuthorised(payer));
208210
}
209211

212+
signer_validator
213+
.validate(&payer, &signer)
214+
.map_err(|_| DipsError::SignerNotAuthorised(signer))?;
215+
210216
if !self.voucher.recipient.eq(expected_payee) {
211217
return Err(DipsError::UnexpectedPayee {
212218
expected: *expected_payee,
@@ -284,14 +290,18 @@ impl CollectionRequest {
284290
}
285291

286292
pub async fn validate_and_create_agreement(
287-
store: Arc<dyn AgreementStore>,
293+
ctx: Arc<DipsServerContext>,
288294
domain: &Eip712Domain,
289295
expected_payee: &Address,
290296
allowed_payers: impl AsRef<[Address]>,
291297
voucher: Vec<u8>,
292-
price_calculator: &PriceCalculator,
293-
ipfs_client: Arc<dyn IpfsFetcher>,
294298
) -> Result<Uuid, DipsError> {
299+
let DipsServerContext {
300+
store,
301+
ipfs_fetcher,
302+
price_calculator,
303+
signer_validator,
304+
} = ctx.as_ref();
295305
let decoded_voucher = SignedIndexingAgreementVoucher::abi_decode(voucher.as_ref(), true)
296306
.map_err(|e| DipsError::AbiDecoding(e.to_string()))?;
297307
let metadata = SubgraphIndexingVoucherMetadata::abi_decode(
@@ -300,9 +310,9 @@ pub async fn validate_and_create_agreement(
300310
)
301311
.map_err(|e| DipsError::AbiDecoding(e.to_string()))?;
302312

303-
decoded_voucher.validate(domain, expected_payee, allowed_payers)?;
313+
decoded_voucher.validate(signer_validator, domain, expected_payee, allowed_payers)?;
304314

305-
let manifest = ipfs_client.fetch(&metadata.subgraphDeploymentId).await?;
315+
let manifest = ipfs_fetcher.fetch(&metadata.subgraphDeploymentId).await?;
306316
match manifest.network() {
307317
Some(chain_id) if chain_id == metadata.chainId => {}
308318
Some(chain_id) => {
@@ -384,9 +394,9 @@ mod test {
384394

385395
pub use crate::store::{AgreementStore, InMemoryAgreementStore};
386396
use crate::{
387-
dips_agreement_eip712_domain, dips_cancellation_eip712_domain, ipfs::TestIpfsClient,
388-
price::PriceCalculator, CancellationRequest, DipsError, IndexingAgreementVoucher,
389-
SignedIndexingAgreementVoucher, SubgraphIndexingVoucherMetadata,
397+
dips_agreement_eip712_domain, dips_cancellation_eip712_domain, server::DipsServerContext,
398+
CancellationRequest, DipsError, IndexingAgreementVoucher, SignedIndexingAgreementVoucher,
399+
SubgraphIndexingVoucherMetadata,
390400
};
391401

392402
#[tokio::test]
@@ -424,22 +434,19 @@ mod test {
424434
let abi_voucher = voucher.abi_encode();
425435
let id = Uuid::from_bytes(voucher.voucher.agreement_id.into());
426436

427-
let store = Arc::new(InMemoryAgreementStore::default());
428-
437+
let ctx = DipsServerContext::for_testing();
429438
let actual_id = super::validate_and_create_agreement(
430-
store.clone(),
439+
ctx.clone(),
431440
&domain,
432441
&payee_addr,
433442
vec![payer_addr],
434443
abi_voucher,
435-
&PriceCalculator::for_testing(),
436-
Arc::new(TestIpfsClient::mainnet()),
437444
)
438445
.await
439446
.unwrap();
440447
assert_eq!(actual_id, id);
441448

442-
let stored_agreement = store.get_by_id(actual_id).await.unwrap().unwrap();
449+
let stored_agreement = ctx.store.get_by_id(actual_id).await.unwrap().unwrap();
443450

444451
assert_eq!(voucher, stored_agreement.voucher);
445452
assert!(!stored_agreement.cancelled);
@@ -448,6 +455,7 @@ mod test {
448455

449456
#[test]
450457
fn voucher_signature_verification() {
458+
let ctx = DipsServerContext::for_testing();
451459
let deployment_id = "Qmbg1qF4YgHjiVfsVt6a13ddrVcRtWyJQfD4LA3CwHM29f".to_string();
452460
let payee = PrivateKeySigner::random();
453461
let payee_addr = payee.address();
@@ -480,18 +488,24 @@ mod test {
480488
let signed = voucher.sign(&domain, payer).unwrap();
481489
assert_eq!(
482490
signed
483-
.validate(&domain, &payee_addr, vec![])
491+
.validate(&ctx.signer_validator, &domain, &payee_addr, vec![])
484492
.unwrap_err()
485493
.to_string(),
486494
DipsError::PayerNotAuthorised(voucher.payer).to_string()
487495
);
488496
assert!(signed
489-
.validate(&domain, &payee_addr, vec![payer_addr])
497+
.validate(
498+
&ctx.signer_validator,
499+
&domain,
500+
&payee_addr,
501+
vec![payer_addr]
502+
)
490503
.is_ok());
491504
}
492505

493506
#[test]
494507
fn check_voucher_modified() {
508+
let ctx = DipsServerContext::for_testing();
495509
let deployment_id = "Qmbg1qF4YgHjiVfsVt6a13ddrVcRtWyJQfD4LA3CwHM29f".to_string();
496510
let payee = PrivateKeySigner::random();
497511
let payee_addr = payee.address();
@@ -526,7 +540,12 @@ mod test {
526540

527541
assert!(matches!(
528542
signed
529-
.validate(&domain, &payee_addr, vec![payer_addr])
543+
.validate(
544+
&ctx.signer_validator,
545+
&domain,
546+
&payee_addr,
547+
vec![payer_addr]
548+
)
530549
.unwrap_err(),
531550
DipsError::PayerNotAuthorised(_)
532551
));
@@ -630,6 +649,7 @@ mod test {
630649

631650
#[tokio::test]
632651
async fn test_create_and_cancel_agreement() -> anyhow::Result<()> {
652+
let ctx = DipsServerContext::for_testing();
633653
let voucher_ctx = VoucherContext::random();
634654
let store = Arc::new(InMemoryAgreementStore::default());
635655

@@ -645,13 +665,11 @@ mod test {
645665

646666
// Create agreement
647667
let agreement_id = super::validate_and_create_agreement(
648-
store.clone(),
668+
ctx.clone(),
649669
&voucher_ctx.domain(),
650670
&voucher_ctx.payee.address(),
651671
vec![voucher_ctx.payer.address()],
652672
signed_voucher.encode_vec(),
653-
&PriceCalculator::for_testing(),
654-
Arc::new(TestIpfsClient::mainnet()),
655673
)
656674
.await?;
657675

@@ -681,8 +699,8 @@ mod test {
681699

682700
#[tokio::test]
683701
async fn test_create_validations_errors() -> anyhow::Result<()> {
702+
let ctx = DipsServerContext::for_testing();
684703
let voucher_ctx = VoucherContext::random();
685-
let store = Arc::new(InMemoryAgreementStore::default());
686704

687705
let metadata = SubgraphIndexingVoucherMetadata {
688706
basePricePerEpoch: U256::from(10000_u64),
@@ -734,13 +752,11 @@ mod test {
734752
let cases = vec![wrong_network_voucher, low_price_voucher, valid_voucher];
735753
for (voucher, result) in cases.into_iter().zip(expected_result.into_iter()) {
736754
let out = super::validate_and_create_agreement(
737-
store.clone(),
755+
ctx.clone(),
738756
&voucher_ctx.domain(),
739757
&voucher_ctx.payee.address(),
740758
vec![voucher_ctx.payer.address()],
741759
voucher.encode_vec(),
742-
&PriceCalculator::for_testing(),
743-
Arc::new(TestIpfsClient::mainnet()),
744760
)
745761
.await;
746762

crates/dips/src/server.rs

+30-13
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,41 @@ use crate::{
1515
CancelAgreementResponse, ProposalResponse, SubmitAgreementProposalRequest,
1616
SubmitAgreementProposalResponse,
1717
},
18+
signers::SignerValidator,
1819
store::AgreementStore,
1920
validate_and_cancel_agreement, validate_and_create_agreement,
2021
};
2122

23+
#[derive(Debug)]
24+
pub struct DipsServerContext {
25+
pub store: Arc<dyn AgreementStore>,
26+
pub ipfs_fetcher: Arc<dyn IpfsFetcher>,
27+
pub price_calculator: PriceCalculator,
28+
pub signer_validator: Arc<dyn SignerValidator>,
29+
}
30+
31+
impl DipsServerContext {
32+
#[cfg(test)]
33+
pub fn for_testing() -> Arc<Self> {
34+
use std::sync::Arc;
35+
36+
use crate::{ipfs::TestIpfsClient, signers, test::InMemoryAgreementStore};
37+
38+
Arc::new(DipsServerContext {
39+
store: Arc::new(InMemoryAgreementStore::default()),
40+
ipfs_fetcher: Arc::new(TestIpfsClient::mainnet()),
41+
price_calculator: PriceCalculator::for_testing(),
42+
signer_validator: Arc::new(signers::NoopSignerValidator),
43+
})
44+
}
45+
}
46+
2247
#[derive(Debug)]
2348
pub struct DipsServer {
24-
pub agreement_store: Arc<dyn AgreementStore>,
49+
pub ctx: Arc<DipsServerContext>,
2550
pub expected_payee: Address,
2651
pub allowed_payers: Vec<Address>,
2752
pub domain: Eip712Domain,
28-
pub ipfs_fetcher: Arc<dyn IpfsFetcher>,
29-
pub price_calculator: PriceCalculator,
3053
}
3154

3255
#[async_trait]
@@ -50,13 +73,11 @@ impl IndexerDipsService for DipsServer {
5073
// - The subgraph deployment is for a chain we support
5174
// - The subgraph deployment is available on IPFS
5275
validate_and_create_agreement(
53-
self.agreement_store.clone(),
76+
self.ctx.clone(),
5477
&self.domain,
5578
&self.expected_payee,
5679
&self.allowed_payers,
5780
signed_voucher,
58-
&self.price_calculator,
59-
self.ipfs_fetcher.clone(),
6081
)
6182
.await
6283
.map_err(Into::<tonic::Status>::into)?;
@@ -80,13 +101,9 @@ impl IndexerDipsService for DipsServer {
80101
return Err(Status::invalid_argument("invalid version"));
81102
}
82103

83-
validate_and_cancel_agreement(
84-
self.agreement_store.clone(),
85-
&self.domain,
86-
signed_cancellation,
87-
)
88-
.await
89-
.map_err(Into::<tonic::Status>::into)?;
104+
validate_and_cancel_agreement(self.ctx.store.clone(), &self.domain, signed_cancellation)
105+
.await
106+
.map_err(Into::<tonic::Status>::into)?;
90107

91108
Ok(tonic::Response::new(CancelAgreementResponse {}))
92109
}

0 commit comments

Comments
 (0)