5050
5151use std:: sync:: Arc ;
5252
53+ use escrow:: { EscrowLookup , EscrowSource } ;
5354use server:: DipsServerContext ;
5455use thegraph_core:: alloy:: {
5556 core:: primitives:: Address ,
@@ -61,6 +62,7 @@ use thegraph_core::alloy::{
6162
6263#[ cfg( feature = "db" ) ]
6364pub mod database;
65+ pub mod escrow;
6466pub mod inflight;
6567pub mod ipfs;
6668pub mod price;
@@ -200,6 +202,10 @@ pub enum DipsError {
200202 SenderNotTrusted { signer : Address } ,
201203 #[ error( "could not verify sender authorisation: {0}" ) ]
202204 TrustVerificationUnavailable ( String ) ,
205+ #[ error( "sender {signer} has insufficient escrow to send agreement proposals" ) ]
206+ InsufficientEscrow { signer : Address } ,
207+ #[ error( "could not verify sender escrow: {0}" ) ]
208+ EscrowVerificationUnavailable ( String ) ,
203209}
204210
205211#[ cfg( feature = "rpc" ) ]
@@ -285,6 +291,25 @@ pub(crate) fn try_extract_deployment_id(rca_bytes: &[u8]) -> Option<String> {
285291 Some ( bytes32_to_ipfs_hash ( & metadata. subgraphDeploymentId . 0 ) )
286292}
287293
294+ /// Authorize an untrusted sender by escrow: a resolved balance at or above
295+ /// `min_wei` passes; below or absent is InsufficientEscrow; a wholesale-empty
296+ /// snapshot is transient, so a funded sender isn't branded unfunded during an outage.
297+ fn verify_escrow (
298+ escrow : & dyn EscrowSource ,
299+ signer : Address ,
300+ min_wei : U256 ,
301+ ) -> Result < ( ) , DipsError > {
302+ match escrow. lookup ( signer) {
303+ EscrowLookup :: Balance ( balance) if balance >= min_wei => Ok ( ( ) ) ,
304+ EscrowLookup :: Balance ( _) | EscrowLookup :: NotFound => {
305+ Err ( DipsError :: InsufficientEscrow { signer } )
306+ }
307+ EscrowLookup :: Unavailable => Err ( DipsError :: EscrowVerificationUnavailable (
308+ "escrow snapshot is empty" . to_string ( ) ,
309+ ) ) ,
310+ }
311+ }
312+
288313/// Validate and create a RecurringCollectionAgreement.
289314///
290315/// Performs validation:
@@ -310,17 +335,27 @@ pub async fn validate_and_create_rca(
310335 additional_networks,
311336 rca_domain,
312337 trusted_signers,
338+ escrow_source,
339+ min_escrow_wei,
313340 } = ctx. as_ref ( ) ;
314341
315342 // Decode SignedRCA
316343 let signed_rca = SignedRecurringCollectionAgreement :: abi_decode ( rca_bytes. as_ref ( ) )
317344 . map_err ( |e| DipsError :: AbiDecoding ( e. to_string ( ) ) ) ?;
318345
319346 // Authenticate then authorize the sender: recover the EIP-712 signer, then
320- // require it to hold the on-chain agreement- manager role before doing any work .
347+ // require it to be an on-chain agreement manager, or else a payer with escrow .
321348 let signer = signed_rca. recover_signer ( rca_domain) ?;
322349 tracing:: debug!( %signer, "recovered RCA signer" ) ;
323- trusted_signers. verify_trusted ( signer) . await ?;
350+ match trusted_signers. verify_trusted ( signer) . await {
351+ Ok ( ( ) ) => { }
352+ // Not a manager: fall back to an escrow check on the recovered signer
353+ // rather than rejecting; a transient role error stays transient.
354+ Err ( DipsError :: SenderNotTrusted { .. } ) => {
355+ verify_escrow ( escrow_source. as_ref ( ) , signer, * min_escrow_wei) ?;
356+ }
357+ Err ( transient) => return Err ( transient) ,
358+ }
324359
325360 // Validate service provider
326361 signed_rca. validate ( expected_service_provider) ?;
@@ -466,6 +501,7 @@ mod test {
466501
467502 use crate :: {
468503 derive_agreement_id,
504+ escrow:: StaticEscrow ,
469505 ipfs:: { FailingIpfsFetcher , MockIpfsFetcher } ,
470506 price:: PriceCalculator ,
471507 rca_eip712_domain,
@@ -513,21 +549,16 @@ mod test {
513549 additional_networks : Arc :: new ( BTreeMap :: new ( ) ) ,
514550 rca_domain : test_rca_domain ( ) ,
515551 trusted_signers : trusted_signers_for_test ( ) ,
552+ escrow_source : Arc :: new ( StaticEscrow :: default ( ) ) ,
553+ min_escrow_wei : U256 :: ZERO ,
516554 } )
517555 }
518556
519- #[ tokio:: test]
520- async fn test_validate_and_create_rca_untrusted_signer_rejected ( ) {
521- let service_provider = Address :: repeat_byte ( 0x11 ) ;
522- let rca = create_test_rca (
523- Address :: repeat_byte ( 0x42 ) ,
524- service_provider,
525- U256 :: from ( 200 ) ,
526- U256 :: from ( 100 ) ,
527- ) ;
528-
529- // Trusted set excludes the test signer, so a valid signature is rejected.
530- let ctx = Arc :: new ( DipsServerContext {
557+ /// A context whose trusted set is empty (so the recovered signer is never a
558+ /// role holder), with a fixed escrow snapshot and minimum, to exercise C's
559+ /// escrow fallback path.
560+ fn untrusted_escrow_ctx ( escrow : StaticEscrow , min_escrow_wei : U256 ) -> Arc < DipsServerContext > {
561+ Arc :: new ( DipsServerContext {
531562 rca_store : Arc :: new ( InMemoryRcaStore :: default ( ) ) ,
532563 ipfs_fetcher : Arc :: new ( MockIpfsFetcher :: default ( ) ) ,
533564 price_calculator : Arc :: new ( PriceCalculator :: new (
@@ -539,12 +570,97 @@ mod test {
539570 additional_networks : Arc :: new ( BTreeMap :: new ( ) ) ,
540571 rca_domain : test_rca_domain ( ) ,
541572 trusted_signers : Arc :: new ( StaticTrustedSigners :: default ( ) ) ,
542- } ) ;
543- let rca_bytes = rca_to_wire_bytes ( rca) ;
573+ escrow_source : Arc :: new ( escrow) ,
574+ min_escrow_wei,
575+ } )
576+ }
544577
545- let result = super :: validate_and_create_rca ( ctx, & service_provider, rca_bytes) . await ;
578+ #[ tokio:: test]
579+ async fn untrusted_sender_with_sufficient_escrow_passes ( ) {
580+ let service_provider = Address :: repeat_byte ( 0x11 ) ;
581+ let rca = create_test_rca (
582+ Address :: repeat_byte ( 0x42 ) ,
583+ service_provider,
584+ U256 :: from ( 200 ) ,
585+ U256 :: from ( 100 ) ,
586+ ) ;
587+ // The recovered signer (test_signer) holds 2 wei; the minimum is 1.
588+ let escrow = StaticEscrow (
589+ [ ( test_signer ( ) . address ( ) , U256 :: from ( 2 ) ) ]
590+ . into_iter ( )
591+ . collect ( ) ,
592+ ) ;
593+ let ctx = untrusted_escrow_ctx ( escrow, U256 :: from ( 1 ) ) ;
594+
595+ let result =
596+ super :: validate_and_create_rca ( ctx, & service_provider, rca_to_wire_bytes ( rca) ) . await ;
597+
598+ assert ! ( result. is_ok( ) , "got: {:?}" , result) ;
599+ }
600+
601+ #[ tokio:: test]
602+ async fn untrusted_sender_below_minimum_is_insufficient ( ) {
603+ let service_provider = Address :: repeat_byte ( 0x11 ) ;
604+ let rca = create_test_rca (
605+ Address :: repeat_byte ( 0x42 ) ,
606+ service_provider,
607+ U256 :: from ( 200 ) ,
608+ U256 :: from ( 100 ) ,
609+ ) ;
610+ let escrow = StaticEscrow (
611+ [ ( test_signer ( ) . address ( ) , U256 :: from ( 1 ) ) ]
612+ . into_iter ( )
613+ . collect ( ) ,
614+ ) ;
615+ let ctx = untrusted_escrow_ctx ( escrow, U256 :: from ( 5 ) ) ;
616+
617+ let result =
618+ super :: validate_and_create_rca ( ctx, & service_provider, rca_to_wire_bytes ( rca) ) . await ;
619+
620+ assert ! ( matches!( result, Err ( DipsError :: InsufficientEscrow { .. } ) ) ) ;
621+ }
546622
547- assert ! ( matches!( result, Err ( DipsError :: SenderNotTrusted { .. } ) ) ) ;
623+ #[ tokio:: test]
624+ async fn untrusted_sender_absent_from_escrow_is_insufficient ( ) {
625+ let service_provider = Address :: repeat_byte ( 0x11 ) ;
626+ let rca = create_test_rca (
627+ Address :: repeat_byte ( 0x42 ) ,
628+ service_provider,
629+ U256 :: from ( 200 ) ,
630+ U256 :: from ( 100 ) ,
631+ ) ;
632+ // Snapshot has a different address, so it is populated but not the signer.
633+ let escrow = StaticEscrow (
634+ [ ( Address :: repeat_byte ( 0xEE ) , U256 :: from ( 99 ) ) ]
635+ . into_iter ( )
636+ . collect ( ) ,
637+ ) ;
638+ let ctx = untrusted_escrow_ctx ( escrow, U256 :: from ( 1 ) ) ;
639+
640+ let result =
641+ super :: validate_and_create_rca ( ctx, & service_provider, rca_to_wire_bytes ( rca) ) . await ;
642+
643+ assert ! ( matches!( result, Err ( DipsError :: InsufficientEscrow { .. } ) ) ) ;
644+ }
645+
646+ #[ tokio:: test]
647+ async fn untrusted_sender_with_empty_snapshot_is_transient ( ) {
648+ let service_provider = Address :: repeat_byte ( 0x11 ) ;
649+ let rca = create_test_rca (
650+ Address :: repeat_byte ( 0x42 ) ,
651+ service_provider,
652+ U256 :: from ( 200 ) ,
653+ U256 :: from ( 100 ) ,
654+ ) ;
655+ let ctx = untrusted_escrow_ctx ( StaticEscrow :: default ( ) , U256 :: from ( 1 ) ) ;
656+
657+ let result =
658+ super :: validate_and_create_rca ( ctx, & service_provider, rca_to_wire_bytes ( rca) ) . await ;
659+
660+ assert ! ( matches!(
661+ result,
662+ Err ( DipsError :: EscrowVerificationUnavailable ( _) )
663+ ) ) ;
548664 }
549665
550666 /// Sign an RCA with the fixed test key over the test domain and ABI-encode
@@ -891,6 +1007,8 @@ mod test {
8911007 additional_networks : Arc :: new ( BTreeMap :: new ( ) ) ,
8921008 rca_domain : test_rca_domain ( ) ,
8931009 trusted_signers : trusted_signers_for_test ( ) ,
1010+ escrow_source : Arc :: new ( StaticEscrow :: default ( ) ) ,
1011+ min_escrow_wei : U256 :: ZERO ,
8941012 } ) ;
8951013
8961014 let rca_bytes = rca_to_wire_bytes ( rca) ;
@@ -1066,6 +1184,8 @@ mod test {
10661184 additional_networks : Arc :: new ( BTreeMap :: new ( ) ) ,
10671185 rca_domain : test_rca_domain ( ) ,
10681186 trusted_signers : trusted_signers_for_test ( ) ,
1187+ escrow_source : Arc :: new ( StaticEscrow :: default ( ) ) ,
1188+ min_escrow_wei : U256 :: ZERO ,
10691189 } ) ;
10701190
10711191 let rca_bytes = rca_to_wire_bytes ( rca) ;
@@ -1102,6 +1222,8 @@ mod test {
11021222 additional_networks : Arc :: new ( BTreeMap :: new ( ) ) ,
11031223 rca_domain : test_rca_domain ( ) ,
11041224 trusted_signers : trusted_signers_for_test ( ) ,
1225+ escrow_source : Arc :: new ( StaticEscrow :: default ( ) ) ,
1226+ min_escrow_wei : U256 :: ZERO ,
11051227 } ) ;
11061228
11071229 let rca_bytes = rca_to_wire_bytes ( rca) ;
@@ -1138,6 +1260,8 @@ mod test {
11381260 additional_networks : Arc :: new ( BTreeMap :: new ( ) ) ,
11391261 rca_domain : test_rca_domain ( ) ,
11401262 trusted_signers : trusted_signers_for_test ( ) ,
1263+ escrow_source : Arc :: new ( StaticEscrow :: default ( ) ) ,
1264+ min_escrow_wei : U256 :: ZERO ,
11411265 } ) ;
11421266
11431267 let rca_bytes = rca_to_wire_bytes ( rca) ;
0 commit comments