From 0ce5cd8d45a7ce5174a79333f51974ca9af2b48b Mon Sep 17 00:00:00 2001 From: Jannis Pohlmann Date: Thu, 21 Sep 2023 13:44:15 +0200 Subject: [PATCH] refactor: Simplify attestation signer creation --- common/src/allocations/mod.rs | 24 ------ common/src/allocations/monitor.rs | 1 + common/src/attestations/signer.rs | 120 ++++++++++++++++++++--------- common/src/attestations/signers.rs | 16 ++-- common/src/lib.rs | 6 +- 5 files changed, 93 insertions(+), 74 deletions(-) diff --git a/common/src/allocations/mod.rs b/common/src/allocations/mod.rs index e32a8b6c4..7a16b1f81 100644 --- a/common/src/allocations/mod.rs +++ b/common/src/allocations/mod.rs @@ -120,30 +120,6 @@ pub fn derive_key_pair( .build()?) } -pub fn allocation_signer(indexer_mnemonic: &str, allocation: &Allocation) -> Result { - // Guess the allocation index by enumerating all indexes in the - // range [0, 100] and checking for a match - for i in 0..100 { - // The allocation was either created at the epoch it intended to or one - // epoch later. So try both both. - for created_at_epoch in [allocation.created_at_epoch, allocation.created_at_epoch - 1] { - let allocation_wallet = derive_key_pair( - indexer_mnemonic, - created_at_epoch, - &allocation.subgraph_deployment.id, - i, - )?; - if allocation_wallet.address().as_fixed_bytes() == allocation.id { - return Ok(allocation_wallet.signer().clone()); - } - } - } - Err(anyhow::anyhow!( - "Could not find allocation signer for allocation {}", - allocation.id - )) -} - #[cfg(test)] mod test { use std::str::FromStr; diff --git a/common/src/allocations/monitor.rs b/common/src/allocations/monitor.rs index 83d398f5c..7be5b9c77 100644 --- a/common/src/allocations/monitor.rs +++ b/common/src/allocations/monitor.rs @@ -61,6 +61,7 @@ async fn current_epoch( .map(|network| network.current_epoch) } +/// An always up-to-date list of an indexer's active and recently closed allocations. pub fn indexer_allocations( network_subgraph: &'static NetworkSubgraph, indexer_address: Address, diff --git a/common/src/attestations/signer.rs b/common/src/attestations/signer.rs index 8b22237f6..277219878 100644 --- a/common/src/attestations/signer.rs +++ b/common/src/attestations/signer.rs @@ -5,13 +5,18 @@ use alloy_primitives::Address; use eip_712_derive::{ sign_typed, Bytes32, DomainSeparator, Eip712Domain, MemberVisitor, StructType, }; -use ethers::utils::hex; +use ethers::{ + signers::{coins_bip39::English, MnemonicBuilder, Signer, Wallet}, + utils::hex, +}; use ethers_core::k256::ecdsa::SigningKey; use ethers_core::types::U256; use keccak_hash::keccak; use secp256k1::SecretKey; use std::convert::TryInto; +use crate::prelude::{Allocation, SubgraphDeploymentID}; + /// An attestation signer tied to a specific allocation via its signer key #[derive(Debug, Clone, PartialEq, Eq)] pub struct AttestationSigner { @@ -22,30 +27,38 @@ pub struct AttestationSigner { impl AttestationSigner { pub fn new( - chain_id: eip_712_derive::U256, + indexer_mnemonic: &str, + allocation: &Allocation, + chain_id: U256, dispute_manager: Address, - signer: SecretKey, - subgraph_deployment_id: Bytes32, - ) -> Self { + ) -> Result { + // Recreate a wallet that has the same address as the allocation + let wallet = wallet_for_allocation(indexer_mnemonic, allocation)?; + + // Extract signer from wallet + let signer = wallet.signer().clone(); + + // Convert chain ID into EIP-712 representation + let mut chain_id_bytes = [0u8; 32]; + chain_id.to_big_endian(&mut chain_id_bytes); + let chain_id = eip_712_derive::U256(chain_id_bytes); + let bytes = hex::decode("a070ffb1cd7409649bf77822cce74495468e06dbfaef09556838bf188679b9c2") .unwrap(); let salt: [u8; 32] = bytes.try_into().unwrap(); - let domain = Eip712Domain { - name: "Graph Protocol".to_owned(), - version: "0".to_owned(), - chain_id, - verifying_contract: eip_712_derive::Address(dispute_manager.into()), - salt, - }; - let domain_separator = DomainSeparator::new(&domain); - - Self { - domain_separator, - signer, - subgraph_deployment_id, - } + Ok(Self { + domain_separator: DomainSeparator::new(&Eip712Domain { + name: "Graph Protocol".to_owned(), + version: "0".to_owned(), + chain_id, + verifying_contract: eip_712_derive::Address(dispute_manager.into()), + salt, + }), + signer: SecretKey::from_slice(&wallet.signer().to_bytes())?, + subgraph_deployment_id: allocation.subgraph_deployment.id.bytes32(), + }) } pub fn create_attestation(&self, request: &str, response: &str) -> Attestation { @@ -101,21 +114,58 @@ pub struct Attestation { pub s: Bytes32, } -/// Helper for creating an AttestationSigner -pub fn create_attestation_signer( - chain_id: U256, - dispute_manager_address: Address, - signer: SigningKey, - deployment_id: [u8; 32], -) -> anyhow::Result { - // Tedious conversions to the "indexer_native" types - let mut chain_id_bytes = [0u8; 32]; - chain_id.to_big_endian(&mut chain_id_bytes); - let signer = AttestationSigner::new( - eip_712_derive::U256(chain_id_bytes), - dispute_manager_address, - secp256k1::SecretKey::from_slice(&signer.to_bytes())?, - deployment_id, +fn derive_key_pair( + indexer_mnemonic: &str, + epoch: u64, + deployment: &SubgraphDeploymentID, + index: u64, +) -> Result, anyhow::Error> { + let mut derivation_path = format!("m/{}/", epoch); + derivation_path.push_str( + &deployment + .ipfs_hash() + .as_bytes() + .iter() + .map(|char| char.to_string()) + .collect::>() + .join("/"), ); - Ok(signer) + derivation_path.push_str(format!("/{}", index).as_str()); + + Ok(MnemonicBuilder::::default() + .derivation_path(&derivation_path) + .expect("Valid derivation path") + .phrase(indexer_mnemonic) + .build()?) +} + +fn wallet_for_allocation( + indexer_mnemonic: &str, + allocation: &Allocation, +) -> Result, anyhow::Error> { + // Guess the allocation index by enumerating all indexes in the + // range [0, 100] and checking for a match + for i in 0..100 { + // The allocation was either created at the epoch it intended to or one + // epoch later. So try both both. + for created_at_epoch in [allocation.created_at_epoch, allocation.created_at_epoch - 1] { + // The allocation ID is the address of a unique key pair, we just + // need to find the right one by enumerating them all + let wallet = derive_key_pair( + indexer_mnemonic, + created_at_epoch, + &allocation.subgraph_deployment.id, + i, + )?; + + // See if we have a match, i.e. a wallet whose address is identical to the allocation ID + if wallet.address().as_fixed_bytes() == allocation.id { + return Ok(wallet); + } + } + } + Err(anyhow::anyhow!( + "Could not generate wallet matching allocation {}", + allocation.id + )) } diff --git a/common/src/attestations/signers.rs b/common/src/attestations/signers.rs index 7e01de95a..d00faab95 100644 --- a/common/src/attestations/signers.rs +++ b/common/src/attestations/signers.rs @@ -12,20 +12,23 @@ use tokio::sync::Mutex; use crate::prelude::{Allocation, AttestationSigner}; -use super::{attestation_signer_for_allocation, signer::create_attestation_signer}; - +/// An always up-to-date list of attestation signers, one for each of the indexer's allocations. pub fn attestation_signers( indexer_allocations: Eventual>, indexer_mnemonic: String, chain_id: U256, dispute_manager: Address, ) -> Eventual> { + // Keep a cache of the most recent 1000 signers around so we don't need to recreate them + // every time there is a small change in the allocations let cache: &'static Mutex> = Box::leak(Box::new(Mutex::new(LruCache::new( NonZeroUsize::new(1000).unwrap(), )))); let indexer_mnemonic = Arc::new(indexer_mnemonic); + // Whenever the indexer's active or recently closed allocations change, make sure + // we have attestation signers for all of them indexer_allocations.map(move |allocations| { let indexer_mnemonic = indexer_mnemonic.clone(); @@ -34,14 +37,7 @@ pub fn attestation_signers( for (id, allocation) in allocations.iter() { let result = cache.try_get_or_insert(*id, || { - attestation_signer_for_allocation(&indexer_mnemonic, allocation).and_then(|signer| { - create_attestation_signer( - chain_id, - dispute_manager, - signer, - allocation.subgraph_deployment.id.bytes32(), - ) - }) + AttestationSigner::new(&indexer_mnemonic, &allocation, chain_id, dispute_manager) }); if let Err(e) = result { diff --git a/common/src/lib.rs b/common/src/lib.rs index c6471949c..41e3f6ec7 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -13,11 +13,7 @@ mod test_vectors; pub mod prelude { pub use super::allocations::monitor::indexer_allocations; pub use super::allocations::{Allocation, AllocationStatus, SubgraphDeployment}; - pub use super::attestations::{ - attestation_signer_for_allocation, - signer::{create_attestation_signer, AttestationSigner}, - signers::attestation_signers, - }; + pub use super::attestations::{signer::AttestationSigner, signers::attestation_signers}; pub use super::network_subgraph::NetworkSubgraph; pub use super::types::*; }