Skip to content

Commit

Permalink
feat: signed voucher handling
Browse files Browse the repository at this point in the history
  • Loading branch information
mangas committed Oct 8, 2024
1 parent f144748 commit e2359ab
Show file tree
Hide file tree
Showing 12 changed files with 822 additions and 128 deletions.
580 changes: 473 additions & 107 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["common", "config", "service", "tap-agent"]
members = ["common", "config", "dips", "service", "tap-agent"]
resolver = "2"

[profile.dev.package."*"]
Expand All @@ -19,6 +19,7 @@ anyhow = { version = "1.0.72" }
thiserror = "1.0.49"
async-trait = "0.1.72"
eventuals = "0.6.7"
base64 = "0.22.1"
reqwest = { version = "0.12", features = [
"charset",
"h2",
Expand Down
7 changes: 7 additions & 0 deletions common/src/indexer_service/http/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ pub struct IndexerServiceConfig {
pub escrow_subgraph: SubgraphConfig,
pub graph_network: GraphNetworkConfig,
pub tap: TapConfig,
pub dips: IndexerDipsConfig,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct IndexerDipsConfig {
pub expected_payee: String,
pub allowed_payers: Vec<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
Expand Down
4 changes: 2 additions & 2 deletions common/src/indexer_service/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ mod static_subgraph;
mod tap_receipt_header;

pub use config::{
DatabaseConfig, GraphNetworkConfig, GraphNodeConfig, IndexerConfig, IndexerServiceConfig,
ServerConfig, SubgraphConfig, TapConfig,
DatabaseConfig, GraphNetworkConfig, GraphNodeConfig, IndexerConfig, IndexerDipsConfig,
IndexerServiceConfig, ServerConfig, SubgraphConfig, TapConfig,
};
pub use indexer_service::{
AttestationOutput, IndexerService, IndexerServiceImpl, IndexerServiceOptions,
Expand Down
8 changes: 8 additions & 0 deletions config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub struct Config {
pub blockchain: BlockchainConfig,
pub service: ServiceConfig,
pub tap: TapConfig,
pub dips: DipsConfig,
}

// Newtype wrapping Config to be able use serde_ignored with Figment
Expand Down Expand Up @@ -307,6 +308,13 @@ pub struct TapConfig {
pub sender_aggregator_endpoints: HashMap<Address, Url>,
}

#[derive(Debug, Deserialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct DipsConfig {
pub expected_payee: String,
pub allowed_payers: Vec<String>,
}

impl TapConfig {
pub fn get_trigger_value(&self) -> u128 {
let grt_wei = self.max_amount_willing_to_lose_grt.get_value();
Expand Down
14 changes: 14 additions & 0 deletions dips/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "indexer-dips"
version = "0.1.0"
edition = "2021"

[dependencies]
alloy-core = "0.8.5"
alloy-primitives = { version = "0.8.5", default-features = true, features = ["rlp"]}
alloy-sol-types = "0.8.5"
alloy-signer = "0.4.2"
alloy-rlp = "0.3.8"

[dev-dependencies]
alloy-signer-local = "0.4.2"
173 changes: 173 additions & 0 deletions dips/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs.
// SPDX-License-Identifier: Apache-2.0

use std::str::FromStr;

pub use alloy_core;
pub use alloy_rlp;
pub use alloy_signer;
pub use alloy_sol_types;

use alloy_core::primitives::Address;
use alloy_rlp::{RlpDecodable, RlpEncodable};
use alloy_signer::Signature;
use alloy_sol_types::{sol, SolStruct};

sol! {
// EIP712 encoded bytes, ABI - ethers
#[derive(Debug, RlpEncodable, RlpDecodable, PartialEq)]
struct SignedIndexingAgreementVoucher {
IndexingAgreementVoucher voucher;
bytes signature;
}

#[derive(Debug, RlpEncodable, RlpDecodable, PartialEq)]
struct IndexingAgreementVoucher {
// should coincide with signer
address payer;
// should coincide with indexer
address payee;
// data service that will initiate payment collection
address service;
// initial indexing amount max
uint256 maxInitialAmount;
uint256 maxOngoingAmountPerEpoch;
// time to accept the agreement, intended to be on the order
// of hours or mins
uint64 deadline;
uint32 maxEpochsPerCollection;
uint32 minEpochsPerCollection;
// after which the agreement is complete
uint32 durationEpochs;
bytes metadata;
}

// the vouchers are generic to each data service, in the case of subgraphs this is an ABI-encoded SubgraphIndexingVoucherMetadata
#[derive(Debug, RlpEncodable, RlpDecodable, PartialEq)]
struct SubgraphIndexingVoucherMetadata {
uint256 pricePerBlock; // wei GRT
bytes32 protocolNetwork; // eip199:1 format
// differentiate based on indexed chain
bytes32 chainId; // eip199:1 format
}
}

impl SignedIndexingAgreementVoucher {
// TODO: Validate all values, maybe return a useful error on failure.
pub fn is_valid(
&self,
expected_payee: &Address,
allowed_payers: impl AsRef<[Address]>,
) -> bool {
let sig = match Signature::from_str(&self.signature.to_string()) {
Ok(s) => s,
Err(_) => return false,
};

let payer = sig
.recover_address_from_msg(self.voucher.eip712_hash_struct())
.unwrap();

if allowed_payers.as_ref().is_empty()
|| !allowed_payers.as_ref().iter().any(|addr| addr.eq(&payer))
{
return false;
}

if !self.voucher.payee.eq(expected_payee) {
return false;
}

true
}
}

#[cfg(test)]
mod test {
use alloy_core::primitives::{Address, FixedBytes, U256};
use alloy_signer::SignerSync;
use alloy_signer_local::PrivateKeySigner;
use alloy_sol_types::SolStruct;

use crate::{
IndexingAgreementVoucher, SignedIndexingAgreementVoucher, SubgraphIndexingVoucherMetadata,
};

#[test]
fn voucher_signature_verification() {
let payee = PrivateKeySigner::random();
let payee_addr = payee.address();
let payer = PrivateKeySigner::random();
let payer_addr = payer.address();

let metadata = SubgraphIndexingVoucherMetadata {
pricePerBlock: U256::from(10000_u64),
protocolNetwork: FixedBytes::left_padding_from("arbitrum-one".as_bytes()),
chainId: FixedBytes::left_padding_from("mainnet".as_bytes()),
};

let voucher = IndexingAgreementVoucher {
payer: payer_addr,
payee: payee.address(),
service: Address(FixedBytes::ZERO),
maxInitialAmount: U256::from(10000_u64),
maxOngoingAmountPerEpoch: U256::from(10000_u64),
deadline: 1000,
maxEpochsPerCollection: 1000,
minEpochsPerCollection: 1000,
durationEpochs: 1000,
metadata: metadata.eip712_hash_struct().to_owned().into(),
};

let signed = SignedIndexingAgreementVoucher {
voucher: voucher.clone(),
signature: payer
.sign_message_sync(voucher.eip712_hash_struct().as_slice())
.unwrap()
.as_bytes()
.into(),
};

assert!(signed.is_valid(&payee_addr, vec![]));
assert!(signed.is_valid(&payee_addr, vec![payer_addr]));
}

#[test]
fn check_voucher_modified() {
let payee = PrivateKeySigner::random();
let payee_addr = payee.address();
let payer = PrivateKeySigner::random();
let payer_addr = payer.address();

let metadata = SubgraphIndexingVoucherMetadata {
pricePerBlock: U256::from(10000_u64),
protocolNetwork: FixedBytes::left_padding_from("arbitrum-one".as_bytes()),
chainId: FixedBytes::left_padding_from("mainnet".as_bytes()),
};

let voucher = IndexingAgreementVoucher {
payer: payer_addr,
payee: payee_addr,
service: Address(FixedBytes::ZERO),
maxInitialAmount: U256::from(10000_u64),
maxOngoingAmountPerEpoch: U256::from(10000_u64),
deadline: 1000,
maxEpochsPerCollection: 1000,
minEpochsPerCollection: 1000,
durationEpochs: 1000,
metadata: metadata.eip712_hash_struct().to_owned().into(),
};

let mut signed = SignedIndexingAgreementVoucher {
voucher: voucher.clone(),
signature: payer
.sign_message_sync(voucher.eip712_hash_struct().as_slice())
.unwrap()
.as_bytes()
.into(),
};
signed.voucher.service = Address::repeat_byte(9);

assert!(signed.is_valid(&payee_addr, vec![payer_addr]));
}
}
3 changes: 3 additions & 0 deletions service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ license = "Apache-2.0"
[dependencies]
indexer-common = { path = "../common" }
indexer-config = { path = "../config" }
indexer-dips = { path = "../dips" }
anyhow = { workspace = true }
prometheus = { workspace = true }
reqwest = { workspace = true }
Expand All @@ -27,10 +28,12 @@ build-info.workspace = true
lazy_static.workspace = true
async-graphql = { version = "7.0.11", default-features = false }
async-graphql-axum = "7.0.11"
base64.workspace = true
graphql = { git = "https://github.com/edgeandnode/toolshed", tag = "graphql-v0.3.0" }

[dev-dependencies]
hex-literal = "0.4.1"
alloy-signer-local = "0.4.2"

[build-dependencies]
build-info-build = { version = "0.0.38", default-features = false }
8 changes: 6 additions & 2 deletions service/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};

use indexer_common::indexer_service::http::{
DatabaseConfig, GraphNetworkConfig, GraphNodeConfig, IndexerConfig, IndexerServiceConfig,
ServerConfig, SubgraphConfig, TapConfig,
DatabaseConfig, GraphNetworkConfig, GraphNodeConfig, IndexerConfig, IndexerDipsConfig,
IndexerServiceConfig, ServerConfig, SubgraphConfig, TapConfig,
};
use indexer_config::Config as MainConfig;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -77,6 +77,10 @@ impl From<MainConfig> for Config {
timestamp_error_tolerance: value.tap.rav_request.timestamp_buffer_secs.as_secs(),
receipt_max_value: value.service.tap.max_receipt_value_grt.get_value(),
},
dips: IndexerDipsConfig {
expected_payee: value.dips.expected_payee,
allowed_payers: value.dips.allowed_payers,
},
})
}
}
1 change: 1 addition & 0 deletions service/src/database/dips.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub trait AgreementStore: Sync + Send {
async fn cancel_agreement(&self, signature: String) -> anyhow::Result<String>;
}

#[derive(Default)]
pub struct InMemoryAgreementStore {
pub data: tokio::sync::RwLock<HashMap<String, Agreement>>,
}
Expand Down
Loading

0 comments on commit e2359ab

Please sign in to comment.