Skip to content

Commit

Permalink
refactor: Simplify attestation signer creation
Browse files Browse the repository at this point in the history
  • Loading branch information
Jannis committed Sep 21, 2023
1 parent cd51ba0 commit 0ce5cd8
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 74 deletions.
24 changes: 0 additions & 24 deletions common/src/allocations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,30 +120,6 @@ pub fn derive_key_pair(
.build()?)
}

pub fn allocation_signer(indexer_mnemonic: &str, allocation: &Allocation) -> Result<SigningKey> {
// 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;
Expand Down
1 change: 1 addition & 0 deletions common/src/allocations/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
120 changes: 85 additions & 35 deletions common/src/attestations/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<Self, anyhow::Error> {
// 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 {
Expand Down Expand Up @@ -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<AttestationSigner> {
// 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<Wallet<SigningKey>, 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::<Vec<String>>()
.join("/"),
);
Ok(signer)
derivation_path.push_str(format!("/{}", index).as_str());

Ok(MnemonicBuilder::<English>::default()
.derivation_path(&derivation_path)
.expect("Valid derivation path")
.phrase(indexer_mnemonic)
.build()?)
}

fn wallet_for_allocation(
indexer_mnemonic: &str,
allocation: &Allocation,
) -> Result<Wallet<SigningKey>, 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
))
}
16 changes: 6 additions & 10 deletions common/src/attestations/signers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<HashMap<Address, Allocation>>,
indexer_mnemonic: String,
chain_id: U256,
dispute_manager: Address,
) -> Eventual<HashMap<Address, AttestationSigner>> {
// 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<LruCache<_, _>> = 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();

Expand All @@ -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 {
Expand Down
6 changes: 1 addition & 5 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
}

0 comments on commit 0ce5cd8

Please sign in to comment.