Description
Problem statement
For facilitating payments for Firehose and Substreams data, we will need to extend the functionality of TAP agent as described in this issue. Note: The bold parts in the use case description are what require new TAP agent functionality.
This assumes that indexers will run e.g. a Firehose Indexer Service that consumers will open a connection with in order to exchange TAP receipts and RAVs. This service will need to know when to request an RAV from a consumer and when to stop serving them.
Unlike with gateways that have a public endpoint to send RAV requests to, the only way to get RAVs from consumers is by "talking" to them directly. We therefore envision roughly the following architecture for this:
flowchart TB
subgraph Indexer
fis[Firehose Indexer Service]
f[Firehose]
end
subgraph Consumer
fc[Firehose Client]
fnc[Firehose Network Client]
end
fc -- Firehose requests --> fnc
fnc -- Data stream --> fc
fis -- Authorization response, receipt & RAV requests --> fnc
fnc -- Authorization request, receipts & RAVs --> fis
fnc -- Firehose requests --> f
f -- Data stream --> fnc
f -- Report bytes sent/read --> fis
How does this work in practice?
- Firehose Client wants to stream some data and makes requests to a local Firehose Network Client.
- Firehose Network Client picks an indexer to use and opens a payment connection with its Firehose Indexer Service.
- Firehose Indexer Service decides whether it still owes the consumer some data from a previous receipt.
- If yes, it sends an authorization message back that includes a Firehose URL and auth token.
- If no, it sends a receipt request back. Once it gets a receipt from the Firehose Network Client, it sends the authorization message.
- Firehose Network Client opens the data connection and starts streaming data from the indexer's Firehose.
- Firehose Indexer Service tracks the bytes served to the consumer against the latest receipt it has received.
- When it has served the data corresponding to the receipt amount, it requests another receipt.
- Firehose Indexer Service also
- periodically checks how much collateral the consumer has remaining. If this ever goes to zero, it instructs the Firehose to terminate the data connection.
- periodically checks whether it needs a RAV from the consumer. When it does, it sends a RAV request to the consumer and waits for a RAV. If it doesn't get one back in a certain time frame, it instructs Firehose to terminate the data connection.
Proposal
We propose that TAP agent serves a gRPC API for Firehose Indexer Services but also Subgraph Indexer Services to connect to. This API could look as follows:
service TAPAgent {
rpc PayerStatus(PayerStatusRequest) returns (PayerStatusResponse);
}
message PayerStatusRequest {
bytes payer_address = 1;
}
message PayerStatusResponse {
bytes payer_address = 1;
bytes remaining_collateral = 2;
optional bytes rav_request = 3;
}
Using this, different indexer service implementations can:
- Decide when to stop serving a consumer (e.g. when their remaining collateral goes to zero).
- Forward RAV requests to the consumer whenever necessary.
The subgraph indexer service could use this by periodically checking the payer status for all gateways it has interacted with. If a RAV is required for any of them, it could then send that request to their aggregator endpoint. This way, TAP agent would not need to know anything about gateways and their URLs.
The Firehose indexer service could use this by periodically checking the payer status for all consumers that have a payment connection open with the indexer. It can forward RAV requests to them via these payment connections.
Alternative considerations
- The above gRPC API may not be ideal for performance reasons. I (Jannis) am not a gRPC/protobuf expert. Perhaps a stream would be better instead of a request/response pattern? 🤔
Additional context
- It may make sense to also use this gRPC API between the subgraph indexer service and TAP agent. The way that could work is that the subgraph indexer service would be the one to have awareness of what gateways exist and what their RAV endpoints are. It could then periodically poll the TAP agent for whether it needs a RAV from any of the gateways, and take action accordingly. This way, the TAP agent would not have to be aware of gateways at all and the way Firehose indexer service, subgraph indexer service and others obtain RAV requests would be uniform.