From 9819d4b5402c9c98e5246efc4842f2ce2996960e Mon Sep 17 00:00:00 2001 From: Miguel de Elias Date: Thu, 29 Feb 2024 12:00:34 -0300 Subject: [PATCH] feat: added support for new SAM contract --- .../abi/SubgraphAvailabilityManager.abi.json | 411 ++++++++++++++++++ availability-oracle/src/contract.rs | 95 +++- availability-oracle/src/main.rs | 80 +++- availability-oracle/src/test.rs | 8 +- 4 files changed, 565 insertions(+), 29 deletions(-) create mode 100644 availability-oracle/src/abi/SubgraphAvailabilityManager.abi.json diff --git a/availability-oracle/src/abi/SubgraphAvailabilityManager.abi.json b/availability-oracle/src/abi/SubgraphAvailabilityManager.abi.json new file mode 100644 index 0000000..fd574b7 --- /dev/null +++ b/availability-oracle/src/abi/SubgraphAvailabilityManager.abi.json @@ -0,0 +1,411 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_governor", + "type": "address" + }, + { + "internalType": "address", + "name": "_rewardsManager", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_executionThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_voteTimeLimit", + "type": "uint256" + }, + { + "internalType": "address[5]", + "name": "_oracles", + "type": "address[5]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "NewOwnership", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "NewPendingOwnership", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "OracleSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "subgraphDeploymentID", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bool", + "name": "deny", + "type": "bool" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "oracleIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "OracleVote", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "voteTimeLimit", + "type": "uint256" + } + ], + "name": "VoteTimeLimitSet", + "type": "event" + }, + { + "inputs": [], + "name": "NUM_ORACLES", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_subgraphDeploymentID", + "type": "bytes32" + }, + { + "internalType": "bool", + "name": "_deny", + "type": "bool" + } + ], + "name": "checkVotes", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "currentNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "executionThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "lastAllowVote", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "lastDenyVote", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "oracles", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingGovernor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_oracle", + "type": "address" + } + ], + "name": "setOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_voteTimeLimit", + "type": "uint256" + } + ], + "name": "setVoteTimeLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newGovernor", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_subgraphDeploymentID", + "type": "bytes32" + }, + { + "internalType": "bool", + "name": "_deny", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "_oracleIndex", + "type": "uint256" + } + ], + "name": "vote", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "_subgraphDeploymentID", + "type": "bytes32[]" + }, + { + "internalType": "bool[]", + "name": "_deny", + "type": "bool[]" + }, + { + "internalType": "uint256", + "name": "_oracleIndex", + "type": "uint256" + } + ], + "name": "voteMany", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "voteTimeLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ] + \ No newline at end of file diff --git a/availability-oracle/src/contract.rs b/availability-oracle/src/contract.rs index 1358cf5..26ca33a 100644 --- a/availability-oracle/src/contract.rs +++ b/availability-oracle/src/contract.rs @@ -4,6 +4,7 @@ use common::prometheus; use ethers::{ abi::Address, contract::abigen, + core::types::U256, middleware::SignerMiddleware, providers::{Http, Middleware, Provider}, signers::{LocalWallet, Signer}, @@ -14,12 +15,16 @@ use std::time::Duration; use url::Url; #[async_trait] -pub trait RewardsManager { +pub trait StateManager { /// Send a transaction to the contract setting the denied status by deployment id. - async fn set_denied_many(&self, denied_status: Vec<([u8; 32], bool)>) -> Result<(), Error>; + async fn deny_many(&self, denied_status: Vec<([u8; 32], bool)>) -> Result<(), Error>; } abigen!(RewardsManagerABI, "src/abi/RewardsManager.abi.json"); +abigen!( + SubgraphAvailabilityManagerABI, + "src/abi/SubgraphAvailabilityManager.abi.json" +); pub struct RewardsManagerContract { contract: RewardsManagerABI, LocalWallet>>, @@ -49,9 +54,46 @@ impl RewardsManagerContract { } } +pub struct SubgraphAvailabilityManagerContract { + contract: SubgraphAvailabilityManagerABI, LocalWallet>>, + oracle_index: u64, + logger: Logger, +} + +impl SubgraphAvailabilityManagerContract { + pub async fn new( + signing_key: &SecretKey, + url: Url, + subgraph_availability_manager_contract: Address, + oracle_index: u64, + logger: Logger, + ) -> Self { + let http_client = reqwest::ClientBuilder::new() + .tcp_nodelay(true) + .timeout(Duration::from_secs(10)) + .build() + .unwrap(); + let provider = Provider::new(Http::new_with_client(url, http_client)); + let chain_id = provider.get_chainid().await.unwrap().as_u64(); + let wallet = LocalWallet::from_bytes(signing_key.as_ref()) + .unwrap() + .with_chain_id(chain_id); + let provider = Arc::new(SignerMiddleware::new(provider, wallet)); + let contract = SubgraphAvailabilityManagerABI::new( + subgraph_availability_manager_contract, + provider.clone(), + ); + Self { + contract, + oracle_index, + logger, + } + } +} + #[async_trait] -impl RewardsManager for RewardsManagerContract { - async fn set_denied_many(&self, denied_status: Vec<([u8; 32], bool)>) -> Result<(), Error> { +impl StateManager for RewardsManagerContract { + async fn deny_many(&self, denied_status: Vec<([u8; 32], bool)>) -> Result<(), Error> { // Based on this gas profile data for `setDeniedMany`: // gas-used,items // 4517721,200 @@ -85,19 +127,56 @@ impl RewardsManager for RewardsManagerContract { } } -pub struct RewardsManagerDryRun { +#[async_trait] +impl StateManager for SubgraphAvailabilityManagerContract { + async fn deny_many(&self, denied_status: Vec<([u8; 32], bool)>) -> Result<(), Error> { + // Based on this gas profile data for `setDeniedMany`: + // gas-used,items + // 4517721,200 + // 2271420,100 + // 474431,20 + // 47642,1 + // + // 100 is considered as a good chunk size. + for chunk in denied_status.chunks(100) { + let ids: Vec<[u8; 32usize]> = chunk.iter().map(|s| s.0).collect(); + let statuses: Vec = chunk.iter().map(|s| s.1).collect(); + let num_subgraphs = ids.len() as u64; + let oracle_index = U256::from(self.oracle_index); + let tx = self + .contract + .vote_many(ids, statuses, oracle_index) + // To avoid gas estimation errors, we use a high enough gas limit for 100 items + .gas(U256::from(3_000_000u64)); + + if let Err(err) = tx.call().await { + let message = err.decode_revert::().unwrap(); + error!(self.logger, "Transaction failed"; + "message" => message, + ); + } else { + tx.send().await?.await?; + METRICS.denied_subgraphs_total.inc_by(num_subgraphs); + } + } + + Ok(()) + } +} + +pub struct StateManagerDryRun { logger: Logger, } -impl RewardsManagerDryRun { +impl StateManagerDryRun { pub fn new(logger: Logger) -> Self { Self { logger } } } #[async_trait] -impl RewardsManager for RewardsManagerDryRun { - async fn set_denied_many(&self, denied_status: Vec<([u8; 32], bool)>) -> Result<(), Error> { +impl StateManager for StateManagerDryRun { + async fn deny_many(&self, denied_status: Vec<([u8; 32], bool)>) -> Result<(), Error> { for (id, deny_status) in denied_status { info!(self.logger, "Change deny status"; "id" => hex::encode(id), diff --git a/availability-oracle/src/main.rs b/availability-oracle/src/main.rs index d0e82b4..300b914 100644 --- a/availability-oracle/src/main.rs +++ b/availability-oracle/src/main.rs @@ -122,15 +122,29 @@ struct Config { )] supported_data_source_kinds: Vec, + #[structopt( + long, + env = "SUBGRAPH_AVAILABILITY_MANAGER_CONTRACT", + help = "The address of the subgraph availability manager contract" + )] + pub subgraph_availability_manager_contract: Option
, + #[structopt( long, env = "REWARDS_MANAGER_CONTRACT", help = "The address of the rewards manager contract" )] - pub rewards_manager_contract: Address, + pub rewards_manager_contract: Option
, #[structopt(long, env = "RPC_URL", help = "RPC url for the network")] pub url: Url, + + #[structopt( + long, + env = "ORACLE_INDEX", + help = "Assigned index for the oracle, to be used when voting on SubgraphAvailabilityManager" + )] + pub oracle_index: Option, } #[tokio::main] @@ -141,20 +155,18 @@ async fn main() -> Result<()> { async fn run(logger: Logger, config: Config) -> Result<()> { let ipfs = IpfsImpl::new(config.ipfs, config.ipfs_concurrency, config.ipfs_timeout); let subgraph = NetworkSubgraphImpl::new(logger.clone(), config.subgraph); - let contract: Box = match config.dry_run { - false => { - let signing_key: &SecretKey = &config.signing_key.unwrap().parse()?; - Box::new( - RewardsManagerContract::new( - signing_key, - config.url, - config.rewards_manager_contract, - logger.clone(), - ) - .await, - ) - } - true => Box::new(RewardsManagerDryRun::new(logger.clone())), + let contract: Box = if config.dry_run { + Box::new(StateManagerDryRun::new(logger.clone())) + } else { + let signing_key: &SecretKey = &config.signing_key.unwrap().parse()?; + state_manager( + config.url, + signing_key, + config.rewards_manager_contract, + config.subgraph_availability_manager_contract, + config.oracle_index, + logger.clone() + ).await.expect("Configuration error: either [`REWARDS_MANAGER_CONTRACT`] or [`SUBGRAPH_AVAILABILITY_MANAGER_CONTRACT` and `ORACLE_INDEX`] must be provided.") }; let grace_period = Duration::from_secs(config.grace_period); @@ -217,6 +229,40 @@ async fn run(logger: Logger, config: Config) -> Result<()> { .await } +// This function is used to create a state manager based on the configuration. +// If subgraph_availability_manager_contract and oracle_index are provided, it will create a SubgraphAvailabilityManagerContract. +// If rewards_manager_contract is provided, it will create a RewardsManagerContract. +// If none of the above are provided, it will return None. +async fn state_manager( + rpc_url: Url, + signing_key: &SecretKey, + rewards_manager_contract: Option
, + subgraph_availability_manager_contract: Option
, + oracle_index: Option, + logger: Logger, +) -> Option> { + if let Some(contract_address) = subgraph_availability_manager_contract { + if let Some(oracle_index) = oracle_index { + let contract = SubgraphAvailabilityManagerContract::new( + signing_key, + rpc_url, + contract_address, + oracle_index, + logger.clone(), + ) + .await; + return Some(Box::new(contract)); + } + } else if let Some(contract_address) = rewards_manager_contract { + let contract = + RewardsManagerContract::new(signing_key, rpc_url, contract_address, logger.clone()) + .await; + return Some(Box::new(contract)); + } + + None +} + /// Does the thing that the availablitiy oracle does, namely: /// 1. Grab the list of all deployments over the curation threshold from the subgraph. /// 2. Check if their availability status changed. @@ -224,7 +270,7 @@ async fn run(logger: Logger, config: Config) -> Result<()> { pub async fn reconcile_deny_list( logger: &Logger, ipfs: &impl Ipfs, - rewards_manager: &dyn contract::RewardsManager, + state_manager: &dyn contract::StateManager, subgraph: Arc, min_signal: u64, grace_period: Duration, @@ -289,7 +335,7 @@ pub async fn reconcile_deny_list( .try_collect() .await?; - rewards_manager.set_denied_many(status_changes).await + state_manager.deny_many(status_changes).await } enum Valid { diff --git a/availability-oracle/src/test.rs b/availability-oracle/src/test.rs index e24b07a..2809766 100644 --- a/availability-oracle/src/test.rs +++ b/availability-oracle/src/test.rs @@ -47,7 +47,7 @@ async fn test_reconcile() { crate::reconcile_deny_list( &common::logging::create_logger(), &MockIpfs, - &MockRewardsManager, + &MockStateManager, Arc::new(MockSubgraph), 0, Duration::default(), @@ -130,11 +130,11 @@ impl Ipfs for MockIpfs { } } -struct MockRewardsManager; +struct MockStateManager; #[async_trait] -impl contract::RewardsManager for MockRewardsManager { - async fn set_denied_many(&self, denied_status: Vec<([u8; 32], bool)>) -> Result<(), Error> { +impl contract::StateManager for MockStateManager { + async fn deny_many(&self, denied_status: Vec<([u8; 32], bool)>) -> Result<(), Error> { let denied_status = denied_status .into_iter() .map(|(id, deny)| {