Skip to content

Commit fd32cad

Browse files
committed
refactor: move escrow monitor into common crate
1 parent a0b1c87 commit fd32cad

File tree

7 files changed

+149
-171
lines changed

7 files changed

+149
-171
lines changed
Lines changed: 90 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
// Copyright 2023-, GraphOps and Semiotic Labs.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
use std::collections::HashMap;
5+
use std::sync::Arc;
6+
47
use alloy_primitives::Address;
58
use anyhow::Result;
6-
use ethereum_types::U256;
7-
use log::{error, info};
9+
use ethers_core::types::U256;
10+
use log::{error, info, warn};
811
use serde::Deserialize;
912
use serde_json::json;
10-
use std::collections::HashMap;
11-
use std::sync::Arc;
1213
use tokio::sync::RwLock;
13-
use toolshed::thegraph::DeploymentId;
1414

15-
use crate::graph_node::GraphNodeInstance;
15+
use crate::prelude::SubgraphClient;
1616

1717
#[derive(Debug)]
1818
struct EscrowMonitorInner {
19-
graph_node: GraphNodeInstance,
20-
escrow_subgraph_deployment: DeploymentId,
19+
escrow_subgraph: &'static SubgraphClient,
2120
indexer_address: Address,
2221
interval_ms: u64,
2322
sender_accounts: Arc<RwLock<HashMap<Address, U256>>>,
@@ -33,16 +32,14 @@ pub struct EscrowMonitor {
3332
#[cfg_attr(test, faux::methods)]
3433
impl EscrowMonitor {
3534
pub async fn new(
36-
graph_node: GraphNodeInstance,
37-
escrow_subgraph_deployment: DeploymentId,
35+
escrow_subgraph: &'static SubgraphClient,
3836
indexer_address: Address,
3937
interval_ms: u64,
4038
) -> Result<Self> {
4139
let sender_accounts = Arc::new(RwLock::new(HashMap::new()));
4240

4341
let inner = Arc::new(EscrowMonitorInner {
44-
graph_node,
45-
escrow_subgraph_deployment,
42+
escrow_subgraph,
4643
indexer_address,
4744
interval_ms,
4845
sender_accounts,
@@ -61,30 +58,34 @@ impl EscrowMonitor {
6158
}
6259

6360
async fn current_accounts(
64-
graph_node: &GraphNodeInstance,
65-
escrow_subgraph_deployment: &DeploymentId,
61+
escrow_subgraph: &'static SubgraphClient,
6662
indexer_address: &Address,
6763
) -> Result<HashMap<Address, U256>> {
64+
// Types for deserializing the network subgraph response
65+
#[derive(Deserialize)]
66+
#[serde(rename_all = "camelCase")]
67+
struct EscrowAccountsResponse {
68+
escrow_accounts: Vec<EscrowAccount>,
69+
}
6870
// These 2 structs are used to deserialize the response from the escrow subgraph.
6971
// Note that U256's serde implementation is based on serializing the internal bytes, not the string decimal
7072
// representation. This is why we deserialize them as strings below.
7173
#[derive(Deserialize)]
72-
struct _Sender {
73-
id: Address,
74-
}
75-
#[derive(Deserialize)]
76-
struct _EscrowAccount {
74+
#[serde(rename_all = "camelCase")]
75+
struct EscrowAccount {
7776
balance: String,
78-
#[serde(rename = "totalAmountThawing")]
7977
total_amount_thawing: String,
80-
sender: _Sender,
78+
sender: Sender,
79+
}
80+
#[derive(Deserialize)]
81+
#[serde(rename_all = "camelCase")]
82+
struct Sender {
83+
id: Address,
8184
}
8285

83-
let res = graph_node
84-
.subgraph_query_raw(
85-
escrow_subgraph_deployment,
86-
serde_json::to_string(&json!({
87-
"query": r#"
86+
let response = escrow_subgraph
87+
.query::<EscrowAccountsResponse>(&json!({
88+
"query": r#"
8889
query ($indexer: ID!) {
8990
escrowAccounts(where: {receiver_: {id: $indexer}}) {
9091
balance
@@ -95,60 +96,54 @@ impl EscrowMonitor {
9596
}
9697
}
9798
"#,
98-
"variables": {
99-
"indexer": indexer_address,
100-
}
101-
}
102-
))
103-
.expect("serialize escrow GraphQL query"),
104-
)
99+
"variables": {
100+
"indexer": indexer_address,
101+
}
102+
}
103+
))
105104
.await?;
106105

107-
let mut res_json: serde_json::Value = serde_json::from_str(res.graphql_response.as_str())
108-
.map_err(|e| {
109-
anyhow::anyhow!(
110-
"Failed to fetch current accounts from escrow subgraph: {}",
111-
e
112-
)
113-
})?;
106+
// If there are any GraphQL errors returned, we'll log them for debugging
107+
if let Some(errors) = response.errors {
108+
warn!(
109+
"Errors encountered fetching escrow accounts for indexer {:?}: {}",
110+
indexer_address,
111+
errors
112+
.into_iter()
113+
.map(|e| e.message)
114+
.collect::<Vec<_>>()
115+
.join(", ")
116+
);
117+
}
114118

115-
let escrow_accounts: Vec<_EscrowAccount> =
116-
serde_json::from_value(res_json["data"]["escrowAccounts"].take()).map_err(|e| {
117-
anyhow::anyhow!(
118-
"Failed to parse current accounts response from escrow subgraph: {}",
119-
e
119+
let sender_accounts = response
120+
.data
121+
.map_or(vec![], |data| data.escrow_accounts)
122+
.iter()
123+
.map(|account| {
124+
let balance = U256::checked_sub(
125+
U256::from_str_radix(&account.balance, 16)?,
126+
U256::from_str_radix(&account.total_amount_thawing, 16)?,
120127
)
121-
})?;
122-
123-
let mut sender_accounts: HashMap<Address, U256> = HashMap::new();
124-
125-
for account in escrow_accounts {
126-
let balance = U256::checked_sub(
127-
U256::from_dec_str(&account.balance)?,
128-
U256::from_dec_str(&account.total_amount_thawing)?,
129-
)
130-
.unwrap_or_else(|| {
131-
error!(
132-
"Balance minus total amount thawing underflowed for account {}. Setting balance to 0, no queries \
133-
will be served for this sender.",
134-
account.sender.id
135-
);
136-
U256::from(0)
137-
});
138-
139-
sender_accounts.insert(account.sender.id, balance);
140-
}
128+
.unwrap_or_else(|| {
129+
warn!(
130+
"Balance minus total amount thawing underflowed for account {}. \
131+
Setting balance to 0, no queries will be served for this sender.",
132+
account.sender.id
133+
);
134+
U256::from(0)
135+
});
136+
137+
Ok((account.sender.id, balance))
138+
})
139+
.collect::<Result<HashMap<_, _>, anyhow::Error>>()?;
141140

142141
Ok(sender_accounts)
143142
}
144143

145144
async fn update_accounts(inner: &Arc<EscrowMonitorInner>) -> Result<(), anyhow::Error> {
146-
*(inner.sender_accounts.write().await) = Self::current_accounts(
147-
&inner.graph_node,
148-
&inner.escrow_subgraph_deployment,
149-
&inner.indexer_address,
150-
)
151-
.await?;
145+
*(inner.sender_accounts.write().await) =
146+
Self::current_accounts(inner.escrow_subgraph, &inner.indexer_address).await?;
152147
Ok(())
153148
}
154149

@@ -181,50 +176,47 @@ impl EscrowMonitor {
181176

182177
#[cfg(test)]
183178
mod tests {
184-
use std::str::FromStr;
185-
186179
use wiremock::matchers::{method, path};
187180
use wiremock::{Mock, MockServer, ResponseTemplate};
188181

189-
use crate::{graph_node, test_vectors};
182+
use crate::test_vectors;
183+
use crate::test_vectors::{ESCROW_SUBGRAPH_DEPLOYMENT, INDEXER_ADDRESS};
190184

191185
use super::*;
192186

193187
#[tokio::test]
194188
async fn test_current_accounts() {
195-
let indexer_address = Address::from_str(test_vectors::INDEXER_ADDRESS).unwrap();
196-
let escrow_subgraph_deployment =
197-
DeploymentId::from_str("Qmb5Ysp5oCUXhLA8NmxmYKDAX2nCMnh7Vvb5uffb9n5vss").unwrap();
198-
189+
// Set up a mock escrow subgraph
199190
let mock_server = MockServer::start().await;
200-
let graph_node = graph_node::GraphNodeInstance::new(&mock_server.uri());
191+
let escrow_subgraph_endpoint = SubgraphClient::local_deployment_endpoint(
192+
&mock_server.uri(),
193+
&test_vectors::ESCROW_SUBGRAPH_DEPLOYMENT,
194+
)
195+
.unwrap();
196+
let escrow_subgraph = Box::leak(Box::new(
197+
SubgraphClient::new(
198+
Some(&mock_server.uri()),
199+
Some(&test_vectors::ESCROW_SUBGRAPH_DEPLOYMENT),
200+
escrow_subgraph_endpoint.as_ref(),
201+
)
202+
.unwrap(),
203+
));
201204

202205
let mock = Mock::given(method("POST"))
203-
.and(path(
204-
"/subgraphs/id/".to_string() + &escrow_subgraph_deployment.to_string(),
205-
))
206+
.and(path(format!(
207+
"/subgraphs/id/{}",
208+
*ESCROW_SUBGRAPH_DEPLOYMENT
209+
)))
206210
.respond_with(
207211
ResponseTemplate::new(200)
208212
.set_body_raw(test_vectors::ESCROW_QUERY_RESPONSE, "application/json"),
209213
);
210214
mock_server.register(mock).await;
211215

212-
let inner = EscrowMonitorInner {
213-
graph_node,
214-
escrow_subgraph_deployment,
215-
indexer_address,
216-
interval_ms: 1000,
217-
sender_accounts: Arc::new(RwLock::new(HashMap::new())),
218-
};
219-
220-
let accounts = EscrowMonitor::current_accounts(
221-
&inner.graph_node,
222-
&inner.escrow_subgraph_deployment,
223-
&inner.indexer_address,
224-
)
225-
.await
226-
.unwrap();
216+
let accounts = EscrowMonitor::current_accounts(escrow_subgraph, &INDEXER_ADDRESS)
217+
.await
218+
.unwrap();
227219

228-
assert_eq!(accounts, test_vectors::expected_escrow_accounts());
220+
assert_eq!(accounts, *test_vectors::ESCROW_ACCOUNTS);
229221
}
230222
}

common/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
pub mod allocations;
55
pub mod attestations;
6+
pub mod escrow_monitor;
67
pub mod graphql;
78
pub mod signature_verification;
89
pub mod subgraph_client;
@@ -17,5 +18,6 @@ pub mod prelude {
1718
pub use super::attestations::{
1819
dispute_manager::dispute_manager, signer::AttestationSigner, signers::attestation_signers,
1920
};
21+
pub use super::escrow_monitor::EscrowMonitor;
2022
pub use super::subgraph_client::SubgraphClient;
2123
}

common/src/test_vectors.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,32 @@ pub const ALLOCATIONS_QUERY_RESPONSE: &str = r#"
8989
}
9090
"#;
9191

92+
pub const ESCROW_QUERY_RESPONSE: &str = r#"
93+
{
94+
"data": {
95+
"escrowAccounts": [
96+
{
97+
"balance": "34",
98+
"totalAmountThawing": "10",
99+
"sender": {
100+
"id": "0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1"
101+
}
102+
},
103+
{
104+
"balance": "42",
105+
"totalAmountThawing": "0",
106+
"sender": {
107+
"id": "0x22d491bde2303f2f43325b2108d26f1eaba1e32b"
108+
}
109+
}
110+
]
111+
}
112+
}
113+
"#;
114+
92115
lazy_static! {
93116
pub static ref NETWORK_SUBGRAPH_DEPLOYMENT: DeploymentId = DeploymentId::from_str("QmU7zqJyHSyUP3yFii8sBtHT8FaJn2WmUnRvwjAUTjwMBP").unwrap();
117+
pub static ref ESCROW_SUBGRAPH_DEPLOYMENT: DeploymentId = DeploymentId::from_str("Qmb5Ysp5oCUXhLA8NmxmYKDAX2nCMnh7Vvb5uffb9n5vss").unwrap();
94118

95119
pub static ref INDEXER_OPERATOR_MNEMONIC: String = String::from(
96120
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
@@ -210,4 +234,9 @@ lazy_static! {
210234
},
211235
),
212236
]);
237+
238+
pub static ref ESCROW_ACCOUNTS: HashMap<Address, U256> = HashMap::from([
239+
(Address::from_str("0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1").unwrap(), U256::from(24)),
240+
(Address::from_str("0x22d491bde2303f2f43325b2108d26f1eaba1e32b").unwrap(), U256::from(42)),
241+
]);
213242
}

service/src/config.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use clap::{command, Args, Parser, ValueEnum};
55

66
use alloy_primitives::Address;
77
use serde::{Deserialize, Serialize};
8+
use toolshed::thegraph::DeploymentId;
89

910
use crate::{query_processor::QueryError, util::init_tracing};
1011

@@ -183,7 +184,7 @@ pub struct NetworkSubgraph {
183184
env = "NETWORK_SUBGRAPH_DEPLOYMENT",
184185
help = "Network subgraph deployment"
185186
)]
186-
pub network_subgraph_deployment: Option<String>,
187+
pub network_subgraph_deployment: Option<DeploymentId>,
187188
#[clap(
188189
long,
189190
value_name = "network-subgraph-endpoint",
@@ -233,18 +234,14 @@ pub struct EscrowSubgraph {
233234
env = "ESCROW_SUBGRAPH_DEPLOYMENT",
234235
help = "Escrow subgraph deployment"
235236
)]
236-
pub escrow_subgraph_deployment: String,
237-
// TODO:
238-
//
239-
// #[clap(
240-
// long,
241-
// value_name = "escrow-subgraph-endpoint",
242-
// env = "ESCROW_SUBGRAPH_ENDPOINT",
243-
// // TODO:
244-
// // default_value_t = String::from("https://api.thegraph.com/subgraphs/name/?????????????"),
245-
// help = "Endpoint to query the network subgraph from"
246-
// )]
247-
// pub escrow_subgraph_endpoint: Option<String>,
237+
pub escrow_subgraph_deployment: Option<DeploymentId>,
238+
#[clap(
239+
long,
240+
value_name = "escrow-subgraph-endpoint",
241+
env = "ESCROW_SUBGRAPH_ENDPOINT",
242+
help = "Endpoint to query the network subgraph from"
243+
)]
244+
pub escrow_subgraph_endpoint: String,
248245
// #[clap(
249246
// long,
250247
// value_name = "escrow-subgraph-auth-token",

0 commit comments

Comments
 (0)