Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/crates/aggregator"
directory: "/crates/bin/aggregator"
schedule:
interval: "daily"

- package-ecosystem: "cargo"
directory: "/crates/bin/escrow_manager"
schedule:
interval: "daily"

Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
outputs:
graph_tally_aggregator: ${{ steps.release-please.outputs['crates/aggregator--tag_name'] }}
graph_tally_aggregator: ${{ steps.release-please.outputs['crates/bin/aggregator--tag_name'] }}
graph_tally_escrow_manager: ${{ steps.release-please.outputs['crates/bin/escrow_manager--tag_name'] }}
steps:
- name: Release please
id: release-please
Expand All @@ -36,7 +37,7 @@ jobs:
if: always() && (needs.release-please.result == 'success' || needs.release-please.result == 'skipped')
strategy:
matrix:
target: [graph_tally_aggregator]
target: [graph_tally_aggregator, graph_tally_escrow_manager]
permissions:
packages: write
steps:
Expand Down Expand Up @@ -88,4 +89,4 @@ jobs:
context: ./
push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
tags: ${{ steps.meta.outputs.tags }}
file: Dockerfile.${{ matrix.target }}
file: docker/Dockerfile.${{ matrix.target }}
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Install protobuf compiler
run: apt-get update && apt-get install libsasl2-dev protobuf-compiler -y
run: apt-get update && apt-get install librdkafka-dev libsasl2-dev pkg-config protobuf-compiler -y
- uses: actions/cache@v3
with:
path: |
Expand All @@ -55,7 +55,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Install protobuf compiler
run: apt-get update && apt-get install libsasl2-dev protobuf-compiler -y
run: apt-get update && apt-get install librdkafka-dev libsasl2-dev pkg-config protobuf-compiler -y
- uses: actions/cache@v3
with:
path: |
Expand All @@ -82,7 +82,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Install protobuf compiler
run: apt-get update && apt-get install libsasl2-dev protobuf-compiler -y
run: apt-get update && apt-get install librdkafka-dev libsasl2-dev pkg-config protobuf-compiler -y
- uses: actions/cache@v3
with:
path: |
Expand Down
3 changes: 2 additions & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
".": "0.0.0",
"crates/aggregator": "0.7.0",
"crates/bin/aggregator": "0.7.0",
"crates/bin/escrow_manager": "1.0.0",
"crates/core": "7.0.0",
"crates/integration_tests": "0.1.28",
"crates/eip712_message": "1.0.0",
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[workspace]
resolver = "2"
members = [
"crates/aggregator",
"crates/bin/aggregator",
"crates/bin/escrow_manager",
"crates/core",
"crates/eip712_message",
"crates/graph",
Expand Down
93 changes: 24 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,83 +1,38 @@
# Graph Tally

| Crate | Latest Version |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **graph_tally_aggregator** | [![GHCR](https://img.shields.io/github/v/release/graphprotocol/graph-tally?filter=graph_tally_aggregator-*&label=ghcr.io)](https://github.com/graphprotocol/graph-tally/pkgs/container/graph_tally_aggregator) |
| **graph_tally_core** | [![GitHub Release](https://img.shields.io/github/v/release/graphprotocol/graph-tally?filter=graph_tally_core-*)](https://github.com/graphprotocol/graph-tally/releases) |
| **graph_tally_eip712_message** | [![GitHub Release](https://img.shields.io/github/v/release/graphprotocol/graph-tally?filter=graph_tally_eip712_message-*)](https://github.com/graphprotocol/graph-tally/releases) |
| **graph_tally_graph** | [![GitHub Release](https://img.shields.io/github/v/release/graphprotocol/graph-tally?filter=graph_tally_graph-*)](https://github.com/graphprotocol/graph-tally/releases) |
| **graph_tally_receipt** | [![GitHub Release](https://img.shields.io/github/v/release/graphprotocol/graph-tally?filter=graph_tally_receipt-*)](https://github.com/graphprotocol/graph-tally/releases) |
Graph Tally (formerly TAP - Timeline Aggregation Protocol) is a trust-minimized payment system between Gateways and Indexers in [The Graph](https://thegraph.com) network. It supports arbitrary data services built on Graph Horizon.

## Overview
## How it works

Graph Tally (formerly TAP - Timeline Aggregation Protocol) facilitates a series of payments from a
sender to a receiver (Receipts), who aggregates these payments into a single
payment (a Receipt Aggregate Voucher, or RAV). This aggregate payment can then be
verified on-chain by a payment verifier, reducing the number of transactions and
simplifying the payment process.
1. **Gateways** send signed Receipts to Indexers alongside each query
2. **Indexers** collect Receipts and periodically request aggregation
3. **Aggregator** bundles Receipts into a signed Receipt Aggregate Voucher (RAV)
4. **Indexers** redeem RAVs on-chain to claim payment from the Gateway's escrow

## Documentation for Individual Components
This reduces on-chain transactions from one-per-query to one-per-aggregation-period, drastically lowering costs while maintaining cryptographic guarantees via EIP-712 signatures.

- [graph_tally_aggregator](crates/aggregator/README.md)
A more detailed specification of the protocol can be found in the following GIPs:
- [GIP-0054](https://github.com/graphprotocol/graph-improvement-proposals/blob/main/gips/0054-timeline-aggregation-protocol.md) - Original TAP specification
- [GIP-0066](https://github.com/graphprotocol/graph-improvement-proposals/blob/main/gips/0066-graph-horizon.md) - Graph Horizon, introducing TAP v2 (renamed to Graph Tally)

## Key Components
## Binaries

- **Sender:** Initiates the payment.
- **Receiver:** Receives the payment.
- **Signers:** Multiple signers authorized by the sender to sign receipts.
- **State Channel:** A one-way channel opened by the sender with the receiver
for sending receipts.
- **Receipt:** A record of payment sent by the sender to the receiver.
- **ReceiptAggregateVoucher (RAV):** A signed message containing the aggregate
value of the receipts.
- **graph_tally_aggregator:** A service managed by the sender that aggregates receipts
on the receiver's request into a signed RAV.
- **EscrowAccount:** An account created in the blockchain to hold funds for
the sender-receiver pair.
These services are run by **Gateway operators**. See each component's README for configuration and deployment details.

## Security Measures
| Binary | Description | Docker Image |
|--------|-------------|--------------|
| [graph_tally_aggregator](crates/bin/aggregator/README.md) | Aggregates Receipts into signed RAVs | [![GHCR](https://img.shields.io/github/v/release/graphprotocol/graph-tally?filter=graph_tally_aggregator-*&label=ghcr.io)](https://github.com/graphprotocol/graph-tally/pkgs/container/graph_tally_aggregator) |
| [graph_tally_escrow_manager](crates/bin/escrow_manager/README.md) | Manages escrow balances for Indexer payments | [![GHCR](https://img.shields.io/github/v/release/graphprotocol/graph-tally?filter=graph_tally_escrow_manager-*&label=ghcr.io)](https://github.com/graphprotocol/graph-tally/pkgs/container/graph_tally_escrow_manager) |

- The protocol uses asymmetric cryptography (ECDSA secp256k1) to sign and
verify messages, ensuring the integrity of receipts and RAVs.
## Libraries

## Process

1. **Opening a State Channel:** A state channel is opened via a blockchain
contract, creating an EscrowAccount for the sender-receiver pair.
2. **Sending Receipts:** The sender sends receipts to the receiver through the
state channel.
3. **Storing Receipts:** The receiver stores the receipts and tracks the
aggregate payment.
4. **Creating a RAV Request:** A RAV request consists of a list of receipts and,
optionally, the previous RAV.
5. **Signing the RAV:** The receiver sends the RAV request to the graph_tally_aggregator,
which signs it into a new RAV.
6. **Tracking Aggregate Value:** The receiver tracks the aggregate value and
new receipts since the last RAV.
7. **Requesting a New RAV:** The receiver sends new receipts and the last RAV
to the graph_tally_aggregator for a new RAV.
8. **Closing the State Channel:** When the allocation period ends, the receiver
can send the last RAV to the blockchain and receive payment from the EscrowAccount.

## Performance Considerations

- The primary performance limitations are the time required to verify receipts
and network limitations for sending requests to the graph_tally_aggregator.

## Use Cases

- Graph Tally is suitable for systems that need unidirectional, parallel
micro-payments that are too expensive to redeem individually on-chain. By
aggregating operations off-chain and redeeming them in one transaction, costs
are drastically reduced.

## Compatibility

- The current implementation is for EVM-compatible blockchains, with most of the
system being off-chain.
| Crate | Version |
|-------|---------|
| [graph_tally_core](crates/core) | [![crates.io](https://img.shields.io/crates/v/graph_tally_core)](https://crates.io/crates/graph_tally_core) |
| [graph_tally_receipt](crates/receipt) | [![crates.io](https://img.shields.io/crates/v/graph_tally_receipt)](https://crates.io/crates/graph_tally_receipt) |
| [graph_tally_graph](crates/graph) | [![crates.io](https://img.shields.io/crates/v/graph_tally_graph)](https://crates.io/crates/graph_tally_graph) |
| [graph_tally_eip712_message](crates/eip712_message) | [![crates.io](https://img.shields.io/crates/v/graph_tally_eip712_message)](https://crates.io/crates/graph_tally_eip712_message) |

## Contributing

Contributions are welcome! Please submit a pull request or open an issue to
discuss potential changes.
Also, make sure to follow the [Contributing Guide](https://github.com/graphprotocol/graph-tally/blob/main/CONTRIBUTING.md).
Contributions are welcome! Please submit a pull request or open an issue to discuss potential changes. See the [Contributing Guide](CONTRIBUTING.md) for details.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ rdkafka.workspace = true
serde.workspace = true
serde_json.workspace = true
strum.workspace = true
graph_tally_core = { path = "../core" }
graph_tally_graph = { path = "../graph" }
graph_tally_core = { path = "../../core" }
graph_tally_graph = { path = "../../graph" }
thegraph-core = { workspace = true, features = ["alloy-eip712"] }
tokio.workspace = true
tonic.workspace = true
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
34 changes: 34 additions & 0 deletions crates/bin/escrow_manager/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[package]
name = "graph_tally_escrow_manager"
version = "1.0.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
readme = "README.md"
description = "Manages Graph Tally escrow balances on behalf of a gateway sender."

[[bin]]
name = "graph_tally_escrow_manager"
path = "src/main.rs"

[dependencies]
alloy = { version = "1.0.3", features = ["contract", "signer-local"] }
anyhow.workspace = true
axum.workspace = true
chrono = { version = "0.4.34", default-features = false, features = ["clock"] }
futures-util.workspace = true
lazy_static.workspace = true
prometheus.workspace = true
prost.workspace = true
rdkafka = { workspace = true, features = ["gssapi", "tracing"] }
reqwest = "0.12.5"
serde.workspace = true
serde_json.workspace = true
serde_with = "3.4.0"
snmalloc-rs = "0.3.4"
thegraph-client-subgraphs = "0.3.2"
titorelli = { git = "https://github.com/edgeandnode/titorelli.git", rev = "4c14fc1" }
tokio = { workspace = true, features = ["net", "rt-multi-thread", "time", "tracing"] }
tracing = "0.1.40"
tracing-subscriber.workspace = true
117 changes: 117 additions & 0 deletions crates/bin/escrow_manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Graph Tally Escrow Manager

This service maintains Graph Tally escrow balances on behalf of a gateway sender.

The following data sources are monitored to guide the allocation of GRT into the [Graph Horizon Escrow contract](https://github.com/graphprotocol/contracts/blob/main/packages/horizon/contracts/payments/PaymentsEscrow.sol):

- [Graph Network Subgraph](https://github.com/graphprotocol/graph-network-subgraph) - for active allocations, escrow accounts, and authorized signers
- Kafka topics for receipts and RAVs - to track outstanding debts from query fees

# Configuration

Configuration options are set via a single JSON file. The structure of the file is defined in [src/config.rs](src/config.rs).

## Key Options

| Field | Description |
|-------|-------------|
| `authorize_signers` | If `true`, automatically authorize signers on startup |
| `dry_run` | If `true`, skip contract calls (useful for testing) |
| `port_metrics` | Port for Prometheus metrics server (default: 9090) |
| `update_interval_seconds` | Polling interval for the main loop |

## Sender and Signers

The sender address used for graph_tally_escrow_manager expects authorizedSigners:

- **Sender**: Requires ETH for transaction gas and GRT to allocate into Graph Tally escrow balances for paying indexers
- **Authorized signer**: Used by the gateway and graph_tally_aggregator to sign receipts and RAVs

When `authorize_signers` is set to `true`, the graph_tally_escrow_manager will automatically setup authorized signers on startup. This requires the secret keys for the authorized signer wallets to be present in the `signers` config field.

## Setting up Authorized Signers Manually

To set up authorized signers for graph_tally_escrow_manager:

1. [Find the `PaymentsEscrow` contract address](https://github.com/graphprotocol/contracts/blob/main/packages/horizon/addresses.json) for your network.
2. Navigate to the relevant blockchain explorer (e.g., https://arbiscan.io/address/0xf6Fcc27aAf1fcD8B254498c9794451d82afC673E).
3. Connect the sender address (the address graph_tally_escrow_manager is running with, or the address whose private key you provide in the `secret_key` field of `graph_tally_escrow_manager` config).
4. Go to the "Write Contract" tab and find the `authorizeSigner` function.
5. Generate proof and proofDeadline using the script below.

```bash
mkdir proof-generator && cd proof-generator
npm init -y
npm install ethers
cat > generateProof.js << EOL
const ethers = require('ethers');

async function generateProof(signerPrivateKey, proofDeadline, senderAddress, chainId) {
const signer = new ethers.Wallet(signerPrivateKey);

const messageHash = ethers.solidityPackedKeccak256(
['uint256', 'uint256', 'address'],
[chainId, proofDeadline, senderAddress]
);

const digest = ethers.hashMessage(ethers.getBytes(messageHash));
const signature = await signer.signMessage(ethers.getBytes(messageHash));

return signature;
}

const signerPrivateKey = process.argv[2];
const senderAddress = process.argv[3];
const chainId = parseInt(process.argv[4]);
const proofDeadline = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now

if (!signerPrivateKey || !senderAddress || !chainId) {
console.error('Usage: node generateProof.js <signerPrivateKey> <senderAddress> <chainId>');
process.exit(1);
}

generateProof(signerPrivateKey, proofDeadline, senderAddress, chainId)
.then(proof => {
console.log('Proof:', proof);
console.log('ProofDeadline:', proofDeadline);
console.log('Human-readable date:', new Date(proofDeadline * 1000).toUTCString());
console.log('Chain ID:', chainId);
})
.catch(error => console.error('Error:', error));
EOL

echo "Setup complete. Run the script with:"
echo "node generateProof.js <authorizedSignerPrivateKey> <senderAddress> <chainId>"
```

6. Pass signerAddress, proofDeadline, and proof to the contract and sign the transaction. Repeat if using multiple authorisedSigners

# Logs

Log levels are controlled by the `RUST_LOG` environment variable ([details](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html)).

Example: `RUST_LOG=info,graph_tally_escrow_manager=debug cargo run -- config.json`

# Metrics

Prometheus metrics are exposed on a separate HTTP server. Configure the port via `port_metrics` in the config file (default: 9090).

```bash
curl http://localhost:9090/metrics
```

### Available Metrics

| Metric | Type | Description |
|--------|------|-------------|
| `escrow_total_debt_grt` | Gauge | Total outstanding debt across all receivers |
| `escrow_total_balance_grt` | Gauge | Total escrow balance across all receivers |
| `escrow_total_adjustment_grt` | Gauge | Total GRT deposited in the last cycle |
| `escrow_receiver_count` | Gauge | Number of receivers being tracked |
| `escrow_loop_duration_seconds` | Histogram | Duration of each polling cycle |
| `escrow_debt_grt{receiver}` | Gauge | Outstanding debt per receiver |
| `escrow_balance_grt{receiver}` | Gauge | Escrow balance per receiver |
| `escrow_adjustment_grt{receiver}` | Gauge | Last adjustment per receiver |
| `escrow_deposit_ok` | Counter | Successful deposit transactions |
| `escrow_deposit_err` | Counter | Failed deposit transactions |
| `escrow_deposit_duration` | Histogram | Deposit transaction duration |
Loading
Loading