diff --git a/.sqlx/query-d7e47b7dd017573de0e46f6f86377f92c832fb3ae514c27a3293cb899e5e246d.json b/.sqlx/query-349bb2ab33307fc52d25d74c8fe736dbfc396116aa2a93bb379029672701cef8.json
similarity index 60%
rename from .sqlx/query-d7e47b7dd017573de0e46f6f86377f92c832fb3ae514c27a3293cb899e5e246d.json
rename to .sqlx/query-349bb2ab33307fc52d25d74c8fe736dbfc396116aa2a93bb379029672701cef8.json
index 6041e7596..0c4418172 100644
--- a/.sqlx/query-d7e47b7dd017573de0e46f6f86377f92c832fb3ae514c27a3293cb899e5e246d.json
+++ b/.sqlx/query-349bb2ab33307fc52d25d74c8fe736dbfc396116aa2a93bb379029672701cef8.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "\n SELECT MAX(id), SUM(value)\n FROM scalar_tap_receipts\n WHERE allocation_id = $1 AND sender_address = $2\n ",
+ "query": "\n SELECT MAX(id), SUM(value)\n FROM scalar_tap_receipts\n WHERE allocation_id = $1 AND sender_address = $2\n ",
"describe": {
"columns": [
{
@@ -25,5 +25,5 @@
null
]
},
- "hash": "d7e47b7dd017573de0e46f6f86377f92c832fb3ae514c27a3293cb899e5e246d"
+ "hash": "349bb2ab33307fc52d25d74c8fe736dbfc396116aa2a93bb379029672701cef8"
}
diff --git a/.sqlx/query-a39cf8e5d4b670583e8a2b0af20c270c3db62668e2df5a2b7e7a2fac0ef69405.json b/.sqlx/query-a39cf8e5d4b670583e8a2b0af20c270c3db62668e2df5a2b7e7a2fac0ef69405.json
new file mode 100644
index 000000000..30e417eb6
--- /dev/null
+++ b/.sqlx/query-a39cf8e5d4b670583e8a2b0af20c270c3db62668e2df5a2b7e7a2fac0ef69405.json
@@ -0,0 +1,41 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "\n UPDATE scalar_tap_latest_ravs\n SET is_last = true\n WHERE allocation_id = $1 AND sender_address = $2\n RETURNING *\n ",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "allocation_id",
+ "type_info": "Bpchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "sender_address",
+ "type_info": "Bpchar"
+ },
+ {
+ "ordinal": 2,
+ "name": "rav",
+ "type_info": "Json"
+ },
+ {
+ "ordinal": 3,
+ "name": "is_last",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Bpchar",
+ "Bpchar"
+ ]
+ },
+ "nullable": [
+ false,
+ false,
+ false,
+ false
+ ]
+ },
+ "hash": "a39cf8e5d4b670583e8a2b0af20c270c3db62668e2df5a2b7e7a2fac0ef69405"
+}
diff --git a/Cargo.lock b/Cargo.lock
index 3584e144c..009937fa9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3025,9 +3025,11 @@ dependencies = [
"rstest 0.18.2",
"serde",
"serde_json",
+ "serde_yaml",
"sqlx",
"tap_aggregator",
"tap_core 0.6.0 (git+https://github.com/semiotic-ai/timeline-aggregation-protocol.git?rev=882ca394444b451538908b9996bf7d45869a1bb9)",
+ "tempfile",
"thiserror",
"tokio",
"toolshed",
@@ -5271,6 +5273,19 @@ dependencies = [
"syn 2.0.38",
]
+[[package]]
+name = "serde_yaml"
+version = "0.9.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574"
+dependencies = [
+ "indexmap 2.0.2",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
[[package]]
name = "service"
version = "0.1.0"
@@ -6567,6 +6582,12 @@ dependencies = [
"void",
]
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
+
[[package]]
name = "untrusted"
version = "0.7.1"
diff --git a/migrations/20230915230734_tap_ravs.up.sql b/migrations/20230915230734_tap_ravs.up.sql
index 0e142d303..9e4fc11ce 100644
--- a/migrations/20230915230734_tap_ravs.up.sql
+++ b/migrations/20230915230734_tap_ravs.up.sql
@@ -2,5 +2,6 @@ CREATE TABLE IF NOT EXISTS scalar_tap_latest_ravs (
allocation_id CHAR(40) NOT NULL,
sender_address CHAR(40) NOT NULL,
rav JSON NOT NULL,
+ is_last BOOLEAN DEFAULT FALSE NOT NULL,
PRIMARY KEY (allocation_id, sender_address)
);
diff --git a/tap_agent/Cargo.toml b/tap_agent/Cargo.toml
index 52bf9864e..5561cdb68 100644
--- a/tap_agent/Cargo.toml
+++ b/tap_agent/Cargo.toml
@@ -25,6 +25,7 @@ log = "0.4.19"
reqwest = "0.11.20"
serde = "1.0.188"
serde_json = "1.0.104"
+serde_yaml = "0.9.25"
sqlx = { version = "0.7.1", features = ["postgres", "runtime-tokio", "bigdecimal", "rust_decimal"] }
tap_aggregator = "0.1.6"
tap_core = { git = "https://github.com/semiotic-ai/timeline-aggregation-protocol.git", rev = "882ca394444b451538908b9996bf7d45869a1bb9" }
@@ -47,3 +48,4 @@ ethers-signers = "2.0.8"
faux = "0.1.10"
indexer-common = { path = "../common", features = ["mock"] }
rstest = "0.18.1"
+tempfile = "3.8.0"
diff --git a/tap_agent/src/agent.rs b/tap_agent/src/agent.rs
index 956fae443..5e39d5a98 100644
--- a/tap_agent/src/agent.rs
+++ b/tap_agent/src/agent.rs
@@ -1,9 +1,10 @@
use std::time::Duration;
-use crate::{config, database, tap::managers};
use alloy_primitives::Address;
use indexer_common::prelude::{escrow_accounts, indexer_allocations, SubgraphClient};
+use crate::{aggregator_endpoints, config, database, tap::managers};
+
pub async fn start_agent(config: &'static config::Cli) {
let pgpool = database::connect(&config.postgres).await;
@@ -38,6 +39,11 @@ pub async fn start_agent(config: &'static config::Cli) {
Duration::from_secs(config.escrow_subgraph.escrow_syncing_interval),
);
+ // TODO: replace with a proper implementation once the gateway registry is ready
+ let sender_aggregator_endpoints = aggregator_endpoints::load_aggregator_endpoints(
+ config.tap.sender_aggregator_endpoints_file.clone(),
+ );
+
let _managers = managers::TapManagers::new(
config,
pgpool,
@@ -50,6 +56,7 @@ pub async fn start_agent(config: &'static config::Cli) {
String::from("0"),
// TODO: tap eip712 verifying contract config
Address::ZERO,
+ sender_aggregator_endpoints,
)
.await;
}
diff --git a/tap_agent/src/aggregator_endpoints.rs b/tap_agent/src/aggregator_endpoints.rs
new file mode 100644
index 000000000..3f2e4e453
--- /dev/null
+++ b/tap_agent/src/aggregator_endpoints.rs
@@ -0,0 +1,44 @@
+/// Load a hashmap of sender addresses and their corresponding aggregator endpoints
+/// from a yaml file. We're using serde_yaml.
+use std::collections::HashMap;
+use std::fs::File;
+use std::io::BufReader;
+use std::path::PathBuf;
+
+use alloy_primitives::Address;
+
+/// Load a hashmap of sender addresses and their corresponding aggregator endpoints
+/// from a yaml file. We're using serde_yaml.
+pub fn load_aggregator_endpoints(file_path: PathBuf) -> HashMap
{
+ let file = File::open(file_path).unwrap();
+ let reader = BufReader::new(file);
+ let endpoints: HashMap = serde_yaml::from_reader(reader).unwrap();
+ endpoints
+}
+
+#[cfg(test)]
+mod tests {
+ use std::{io::Write, str::FromStr};
+
+ use super::*;
+
+ /// Test that we can load the aggregator endpoints from a yaml file.
+ /// The test is going to create a temporary yaml file using tempfile, load it, and
+ /// check that the endpoints are loaded correctly.
+ #[test]
+ fn test_load_aggregator_endpoints() {
+ let named_temp_file = tempfile::NamedTempFile::new().unwrap();
+ let mut temp_file = named_temp_file.reopen().unwrap();
+ let yaml = r#"
+ 0xdeadbeefcafebabedeadbeefcafebabedeadbeef: https://example.com/aggregate-receipts
+ 0x0123456789abcdef0123456789abcdef01234567: https://other.example.com/aggregate-receipts
+ "#;
+ temp_file.write_all(yaml.as_bytes()).unwrap();
+ let endpoints = load_aggregator_endpoints(named_temp_file.path().to_path_buf());
+ assert_eq!(
+ endpoints
+ .get(&Address::from_str("0xdeadbeefcafebabedeadbeefcafebabedeadbeef").unwrap()),
+ Some(&"https://example.com/aggregate-receipts".to_string())
+ );
+ }
+}
diff --git a/tap_agent/src/config.rs b/tap_agent/src/config.rs
index 9c63dfd42..984e3f0ff 100644
--- a/tap_agent/src/config.rs
+++ b/tap_agent/src/config.rs
@@ -1,4 +1,5 @@
-// Copyright 2023-, GraphOps and Semiotic Labs. SPDX-License-Identifier: Apache-2.0
+use std::path::PathBuf;
+
use alloy_primitives::Address;
use clap::{command, Args, Parser, ValueEnum};
use dotenvy::dotenv;
@@ -261,8 +262,8 @@ pub struct Tap {
long,
value_name = "rav-request-trigger-value",
env = "RAV_REQUEST_TRIGGER_VALUE",
- help = "Value of unaggregated fees that triggers a RAV request (in GRT).",
- default_value_t = 10
+ help = "Value of unaggregated fees that triggers a RAV request (in GRT wei).",
+ default_value_t = 10_000_000_000_000_000_000 // 10 GRT
)]
pub rav_request_trigger_value: u64,
#[clap(
@@ -271,9 +272,18 @@ pub struct Tap {
env = "RAV_REQUEST_TIMESTAMP_BUFFER",
help = "Buffer (in ns) to add between the current time and the timestamp of the \
last unaggregated fee when triggering a RAV request.",
- default_value_t = 1000
+ default_value_t = 1_000_000_000 // 1 second
)]
pub rav_request_timestamp_buffer_ns: u64,
+
+ // TODO: Remove this whenever the the gateway registry is ready
+ #[clap(
+ long,
+ value_name = "sender-aggregator-endpoints",
+ env = "SENDER_AGGREGATOR_ENDPOINTS",
+ help = "YAML file with a map of sender addresses to aggregator endpoints."
+ )]
+ pub sender_aggregator_endpoints_file: PathBuf,
}
/// Sets up tracing, allows log level to be set from the environment variables
diff --git a/tap_agent/src/database.rs b/tap_agent/src/database.rs
index a2701843f..ef212231a 100644
--- a/tap_agent/src/database.rs
+++ b/tap_agent/src/database.rs
@@ -1,10 +1,11 @@
-// Copyright 2023-, GraphOps and Semiotic Labs. SPDX-License-Identifier:
-// Apache-2.0 TODO: DEDUPLICATE
-use crate::config;
-use sqlx::{postgres::PgPoolOptions, PgPool};
+// TODO: DEDUPLICATE
use std::time::Duration;
+
+use sqlx::{postgres::PgPoolOptions, PgPool};
use tracing::debug;
+use crate::config;
+
pub async fn connect(config: &config::Postgres) -> PgPool {
let url = format!(
"postgresql://{}:{}@{}:{}/{}",
diff --git a/tap_agent/src/main.rs b/tap_agent/src/main.rs
index d7a6f7cbf..db890dd65 100644
--- a/tap_agent/src/main.rs
+++ b/tap_agent/src/main.rs
@@ -1,11 +1,12 @@
-// Copyright 2023-, GraphOps and Semiotic Labs. SPDX-License-Identifier: Apache-2.0
-use crate::config::Cli;
use anyhow::Result;
use lazy_static::lazy_static;
use log::{debug, info};
use tokio::signal::unix::{signal, SignalKind};
+use crate::config::Cli;
+
mod agent;
+mod aggregator_endpoints;
mod config;
mod database;
mod tap;
diff --git a/tap_agent/src/tap/escrow_adapter.rs b/tap_agent/src/tap/escrow_adapter.rs
index 977f25d67..66b48757f 100644
--- a/tap_agent/src/tap/escrow_adapter.rs
+++ b/tap_agent/src/tap/escrow_adapter.rs
@@ -1,10 +1,9 @@
+use std::{collections::HashMap, sync::Arc};
+
use alloy_primitives::Address;
use async_trait::async_trait;
use ethereum_types::U256;
use eventuals::Eventual;
-
-/// TODO: Implement the escrow adapter. This is only a basic mock implementation.
-use std::{collections::HashMap, sync::Arc};
use tap_core::adapters::escrow_adapter::EscrowAdapter as EscrowAdapterTrait;
use thiserror::Error;
use tokio::sync::RwLock;
diff --git a/tap_agent/src/tap/manager.rs b/tap_agent/src/tap/manager.rs
index d1b772995..740f178b8 100644
--- a/tap_agent/src/tap/manager.rs
+++ b/tap_agent/src/tap/manager.rs
@@ -1,21 +1,12 @@
use std::{collections::HashMap, sync::Arc};
-use super::managers::NewReceiptNotification;
-use crate::{
- config::{self},
- tap::{
- escrow_adapter::EscrowAdapter, rav_storage_adapter::RAVStorageAdapter,
- receipt_checks_adapter::ReceiptChecksAdapter,
- receipt_storage_adapter::ReceiptStorageAdapter,
- },
-};
use alloy_primitives::Address;
use alloy_sol_types::Eip712Domain;
use ethereum_types::U256;
use eventuals::Eventual;
use indexer_common::subgraph_client::SubgraphClient;
use jsonrpsee::{core::client::ClientT, http_client::HttpClientBuilder, rpc_params};
-use log::error;
+use log::{error, warn};
use sqlx::PgPool;
use tap_aggregator::jsonrpsee_helpers::JsonRpcResponse;
use tap_core::{
@@ -23,6 +14,17 @@ use tap_core::{
receipt_aggregate_voucher::ReceiptAggregateVoucher, tap_manager::RAVRequest,
tap_receipt::get_full_list_of_checks,
};
+use tokio::sync::Mutex;
+
+use super::managers::NewReceiptNotification;
+use crate::{
+ config::{self},
+ tap::{
+ escrow_adapter::EscrowAdapter, rav_storage_adapter::RAVStorageAdapter,
+ receipt_checks_adapter::ReceiptChecksAdapter,
+ receipt_storage_adapter::ReceiptStorageAdapter,
+ },
+};
type TapManager = tap_core::tap_manager::Manager<
EscrowAdapter,
@@ -31,28 +33,45 @@ type TapManager = tap_core::tap_manager::Manager<
RAVStorageAdapter,
>;
-pub struct Manager {
+#[derive(Default)]
+struct UnaggregatedFees {
+ pub value: u128,
+ pub last_id: u64,
+}
+
+#[derive(Debug, PartialEq)]
+enum State {
+ Running,
+ LastRavPending,
+ Finished,
+}
+
+struct Inner {
pgpool: PgPool,
- tap_manager: Arc,
+ tap_manager: TapManager,
allocation_id: Address,
sender: Address,
sender_aggregator_endpoint: String,
- unaggregated_fees: u128,
- unaggregated_fees_last_id: u64,
- rav_requester_task: Option>,
+ unaggregated_fees: Arc>,
+ state: Arc>,
config: &'static config::Cli,
}
+pub struct Manager {
+ inner: Arc,
+ rav_requester_task: Mutex