mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-01-06 19:38:05 +00:00
Add payments config and charge according to configured rates (#395)
* Track amount paid on payment channels and charge as per query cost * Add payments manager config * Fix received payment checks * Separate out payment not received and amount insufficient errors * Serve a query for free if its rate is not configured * Load open payment channels from the stored state * Take list of free queries from config * Take mutation rates from config * Rename query rates config fields * Take cache settings and request timeout from config * Upgrade ts-nitro packages * Upgrade package versions
This commit is contained in:
parent
aa10a2dc05
commit
198d3e65db
@ -2,7 +2,7 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "0.2.49",
|
||||
"version": "0.2.50",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"command": {
|
||||
|
2
packages/cache/package.json
vendored
2
packages/cache/package.json
vendored
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cerc-io/cache",
|
||||
"version": "0.2.49",
|
||||
"version": "0.2.50",
|
||||
"description": "Generic object cache",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cerc-io/cli",
|
||||
"version": "0.2.49",
|
||||
"version": "0.2.50",
|
||||
"main": "dist/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
@ -11,8 +11,8 @@
|
||||
"chat": "DEBUG='vulcanize:*, laconic:*' node dist/chat.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cerc-io/peer": "^0.2.49",
|
||||
"@cerc-io/util": "^0.2.49",
|
||||
"@cerc-io/peer": "^0.2.50",
|
||||
"@cerc-io/util": "^0.2.50",
|
||||
"@ethersproject/providers": "^5.4.4",
|
||||
"@graphql-tools/utils": "^9.1.1",
|
||||
"@ipld/dag-cbor": "^8.0.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cerc-io/codegen",
|
||||
"version": "0.2.49",
|
||||
"version": "0.2.50",
|
||||
"description": "Code generator",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
@ -20,7 +20,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/cerc-io/watcher-ts#readme",
|
||||
"dependencies": {
|
||||
"@cerc-io/util": "^0.2.49",
|
||||
"@cerc-io/util": "^0.2.50",
|
||||
"@graphql-tools/load-files": "^6.5.2",
|
||||
"@poanet/solidity-flattener": "https://github.com/vulcanize/solidity-flattener.git",
|
||||
"@solidity-parser/parser": "^0.13.2",
|
||||
|
@ -41,12 +41,12 @@
|
||||
"homepage": "https://github.com/cerc-io/watcher-ts#readme",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.3.19",
|
||||
"@cerc-io/cli": "^0.2.49",
|
||||
"@cerc-io/ipld-eth-client": "^0.2.49",
|
||||
"@cerc-io/solidity-mapper": "^0.2.49",
|
||||
"@cerc-io/util": "^0.2.49",
|
||||
"@cerc-io/cli": "^0.2.50",
|
||||
"@cerc-io/ipld-eth-client": "^0.2.50",
|
||||
"@cerc-io/solidity-mapper": "^0.2.50",
|
||||
"@cerc-io/util": "^0.2.50",
|
||||
{{#if (subgraphPath)}}
|
||||
"@cerc-io/graph-node": "^0.2.49",
|
||||
"@cerc-io/graph-node": "^0.2.50",
|
||||
{{/if}}
|
||||
"@ethersproject/providers": "^5.4.4",
|
||||
"apollo-type-bigint": "^0.1.3",
|
||||
|
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@cerc-io/graph-node",
|
||||
"version": "0.2.49",
|
||||
"version": "0.2.50",
|
||||
"main": "dist/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"devDependencies": {
|
||||
"@cerc-io/solidity-mapper": "^0.2.49",
|
||||
"@cerc-io/solidity-mapper": "^0.2.50",
|
||||
"@ethersproject/providers": "^5.4.4",
|
||||
"@graphprotocol/graph-ts": "^0.22.0",
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.2",
|
||||
@ -51,9 +51,9 @@
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.3.19",
|
||||
"@cerc-io/assemblyscript": "0.19.10-watcher-ts-0.1.2",
|
||||
"@cerc-io/cache": "^0.2.49",
|
||||
"@cerc-io/ipld-eth-client": "^0.2.49",
|
||||
"@cerc-io/util": "^0.2.49",
|
||||
"@cerc-io/cache": "^0.2.50",
|
||||
"@cerc-io/ipld-eth-client": "^0.2.50",
|
||||
"@cerc-io/util": "^0.2.50",
|
||||
"@types/json-diff": "^0.5.2",
|
||||
"@types/yargs": "^17.0.0",
|
||||
"bn.js": "^4.11.9",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cerc-io/ipld-eth-client",
|
||||
"version": "0.2.49",
|
||||
"version": "0.2.50",
|
||||
"description": "IPLD ETH Client",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
@ -20,7 +20,7 @@
|
||||
"homepage": "https://github.com/cerc-io/watcher-ts#readme",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.1",
|
||||
"@cerc-io/cache": "^0.2.49",
|
||||
"@cerc-io/cache": "^0.2.50",
|
||||
"cross-fetch": "^3.1.4",
|
||||
"debug": "^4.3.1",
|
||||
"ethers": "^5.4.4",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cerc-io/peer",
|
||||
"version": "0.2.49",
|
||||
"version": "0.2.50",
|
||||
"description": "libp2p module",
|
||||
"main": "dist/index.js",
|
||||
"exports": "./dist/index.js",
|
||||
|
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@cerc-io/solidity-mapper",
|
||||
"version": "0.2.49",
|
||||
"version": "0.2.50",
|
||||
"main": "dist/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"devDependencies": {
|
||||
"@cerc-io/ipld-eth-client": "^0.2.49",
|
||||
"@cerc-io/ipld-eth-client": "^0.2.50",
|
||||
"@ethersproject/abi": "^5.3.0",
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.2",
|
||||
"@nomiclabs/hardhat-waffle": "^2.0.1",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cerc-io/test",
|
||||
"version": "0.2.49",
|
||||
"version": "0.2.50",
|
||||
"main": "dist/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cerc-io/tracing-client",
|
||||
"version": "0.2.49",
|
||||
"version": "0.2.50",
|
||||
"description": "ETH VM tracing client",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@cerc-io/util",
|
||||
"version": "0.2.49",
|
||||
"version": "0.2.50",
|
||||
"main": "dist/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@apollo/utils.keyvaluecache": "^1.0.1",
|
||||
"@cerc-io/nitro-client": "^0.1.4",
|
||||
"@cerc-io/solidity-mapper": "^0.2.49",
|
||||
"@cerc-io/nitro-client": "^0.1.5",
|
||||
"@cerc-io/solidity-mapper": "^0.2.50",
|
||||
"@cerc-io/ts-channel": "1.0.3-ts-nitro-0.1.1",
|
||||
"@ethersproject/providers": "^5.4.4",
|
||||
"@graphql-tools/schema": "^9.0.10",
|
||||
@ -40,8 +40,8 @@
|
||||
"yargs": "^17.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cerc-io/cache": "^0.2.49",
|
||||
"@cerc-io/ipld-eth-client": "^0.2.49",
|
||||
"@cerc-io/cache": "^0.2.50",
|
||||
"@cerc-io/ipld-eth-client": "^0.2.50",
|
||||
"@nomiclabs/hardhat-waffle": "^2.0.1",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
|
@ -81,7 +81,7 @@ interface L2TxsConfig {
|
||||
privateKey: string;
|
||||
|
||||
// Gas limit for tx
|
||||
gasLimit?: number
|
||||
gasLimit?: number;
|
||||
}
|
||||
|
||||
// Peer config
|
||||
@ -126,11 +126,37 @@ export interface PeerConfig {
|
||||
l2TxsConfig?: L2TxsConfig;
|
||||
}
|
||||
|
||||
export interface BaseRatesConfig {
|
||||
freeQueriesLimit: number;
|
||||
freeQueriesList: string[];
|
||||
queries: { [key: string]: string };
|
||||
mutations: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface PaymentsCacheConfig {
|
||||
maxAccounts: number;
|
||||
accountTTLInSecs: number;
|
||||
maxVouchersPerAccount: number;
|
||||
voucherTTLInSecs: number;
|
||||
maxPaymentChannels: number;
|
||||
paymentChannelTTLInSecs: number;
|
||||
}
|
||||
|
||||
// Payments manager config
|
||||
export interface PaymentsConfig {
|
||||
ratesFile: string;
|
||||
requestTimeoutInSecs: number;
|
||||
cache: PaymentsCacheConfig;
|
||||
}
|
||||
|
||||
// ts-nitro config
|
||||
export interface NitroConfig {
|
||||
store: string
|
||||
privateKey: string
|
||||
chainPrivateKey: string
|
||||
store: string;
|
||||
|
||||
privateKey: string;
|
||||
chainPrivateKey: string;
|
||||
|
||||
payments: PaymentsConfig;
|
||||
}
|
||||
|
||||
// P2P config
|
||||
@ -179,7 +205,7 @@ export interface ServerConfig {
|
||||
|
||||
// Flag to specify whether RPC endpoint supports block hash as block tag parameter
|
||||
// https://ethereum.org/en/developers/docs/apis/json-rpc/#default-block
|
||||
rpcSupportsBlockHashParam: boolean
|
||||
rpcSupportsBlockHashParam: boolean;
|
||||
}
|
||||
|
||||
export interface UpstreamConfig {
|
||||
|
@ -7,7 +7,9 @@ import { Response as HTTPResponse } from 'apollo-server-env';
|
||||
import Channel from '@cerc-io/ts-channel';
|
||||
import type { ReadWriteChannel } from '@cerc-io/ts-channel';
|
||||
import type { Client, Voucher } from '@cerc-io/nitro-client';
|
||||
import { utils as nitroUtils } from '@cerc-io/nitro-client';
|
||||
import { utils as nitroUtils, ChannelStatus } from '@cerc-io/nitro-client';
|
||||
|
||||
import { BaseRatesConfig, PaymentsConfig } from './config';
|
||||
|
||||
const log = debug('laconic:payments');
|
||||
|
||||
@ -17,7 +19,8 @@ const PAYMENT_HEADER_REGEX = /vhash:(.*),vsig:(.*)/;
|
||||
|
||||
const ERR_FREE_QUOTA_EXHUASTED = 'Free quota exhausted';
|
||||
const ERR_PAYMENT_NOT_RECEIVED = 'Payment not received';
|
||||
const HTTP_CODE_PAYMENT_NOT_RECEIVED = 402; // Payment required
|
||||
const ERR_AMOUNT_INSUFFICIENT = 'Payment amount insufficient';
|
||||
const HTTP_CODE_PAYMENT_REQUIRED = 402; // Payment required
|
||||
|
||||
const ERR_HEADER_MISSING = 'Payment header x-payment not set';
|
||||
const ERR_INVALID_PAYMENT_HEADER = 'Invalid payment header format';
|
||||
@ -25,41 +28,72 @@ const HTTP_CODE_BAD_REQUEST = 400; // Bad request
|
||||
|
||||
const EMPTY_VOUCHER_HASH = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'; // keccak256('0x')
|
||||
|
||||
// TODO: Configure
|
||||
const LRU_CACHE_MAX_ACCOUNT_COUNT = 1000;
|
||||
const LRU_CACHE_ACCOUNT_TTL = 30 * 60 * 1000; // 30mins
|
||||
const LRU_CACHE_MAX_VOUCHER_COUNT = 1000;
|
||||
const LRU_CACHE_VOUCHER_TTL = 5 * 60 * 1000; // 5mins
|
||||
// Config Defaults
|
||||
const DEFAULT_REQUEST_TIMEOUT = 10; // 10 seconds
|
||||
|
||||
const FREE_QUERY_LIMIT = 10;
|
||||
const FREE_QUERIES = ['latestBlock'];
|
||||
const DEFAULT_FREE_QUERIES_LIMIT = 10;
|
||||
|
||||
const REQUEST_TIMEOUT = 10 * 1000; // 10 seconds
|
||||
const DEFAULT_FREE_QUERIES_LIST = ['latestBlock'];
|
||||
|
||||
const DEFAULT_LRU_CACHE_MAX_ACCOUNTS = 1000;
|
||||
const DEFAULT_LRU_CACHE_ACCOUNT_TTL = 30 * 60; // 30mins
|
||||
const DEFAULT_LRU_CACHE_MAX_VOUCHERS_PER_ACCOUNT = 1000;
|
||||
const DEFAULT_LRU_CACHE_VOUCHER_TTL = 5 * 60; // 5mins
|
||||
const DEFAULT_LRU_CACHE_MAX_PAYMENT_CHANNELS = 10000;
|
||||
const DEFAULT_LRU_CACHE_PAYMENT_CHANNEL_TTL = DEFAULT_LRU_CACHE_ACCOUNT_TTL;
|
||||
|
||||
interface Payment {
|
||||
voucher: Voucher;
|
||||
amount: bigint;
|
||||
}
|
||||
|
||||
export class PaymentsManager {
|
||||
clientAddress?: string;
|
||||
|
||||
private config: PaymentsConfig;
|
||||
private ratesConfig: BaseRatesConfig;
|
||||
|
||||
// TODO: Persist data
|
||||
private remainingFreeQueriesMap: Map<string, number> = new Map();
|
||||
|
||||
private receivedVouchers: LRUCache<string, LRUCache<string, Voucher>>;
|
||||
// TODO: Persist data
|
||||
private receivedPayments: LRUCache<string, LRUCache<string, Payment>>;
|
||||
private paidSoFarOnChannel: LRUCache<string, bigint>;
|
||||
|
||||
private stopSubscriptionLoop: ReadWriteChannel<void>;
|
||||
private paymentListeners: ReadWriteChannel<string>[] = [];
|
||||
|
||||
// TODO: Read query rate map from config
|
||||
// TODO: Add a method to get rate for a query
|
||||
constructor (config: PaymentsConfig, baseRatesConfig: BaseRatesConfig) {
|
||||
this.config = config;
|
||||
this.ratesConfig = baseRatesConfig;
|
||||
|
||||
constructor () {
|
||||
this.receivedVouchers = new LRUCache<string, LRUCache<string, Voucher>>({
|
||||
max: LRU_CACHE_MAX_ACCOUNT_COUNT,
|
||||
ttl: LRU_CACHE_ACCOUNT_TTL
|
||||
this.receivedPayments = new LRUCache<string, LRUCache<string, Payment>>({
|
||||
max: this.config.cache.maxAccounts ?? DEFAULT_LRU_CACHE_MAX_ACCOUNTS,
|
||||
ttl: (this.config.cache.accountTTLInSecs ?? DEFAULT_LRU_CACHE_ACCOUNT_TTL) * 1000
|
||||
});
|
||||
|
||||
this.paidSoFarOnChannel = new LRUCache<string, bigint>({
|
||||
max: this.config.cache.maxPaymentChannels ?? DEFAULT_LRU_CACHE_MAX_PAYMENT_CHANNELS,
|
||||
ttl: (this.config.cache.paymentChannelTTLInSecs ?? DEFAULT_LRU_CACHE_PAYMENT_CHANNEL_TTL) * 1000
|
||||
});
|
||||
|
||||
this.stopSubscriptionLoop = Channel();
|
||||
}
|
||||
|
||||
get freeQueriesList (): string[] {
|
||||
return this.ratesConfig.freeQueriesList ?? DEFAULT_FREE_QUERIES_LIST;
|
||||
}
|
||||
|
||||
get mutationRates (): { [key: string]: string } {
|
||||
return this.ratesConfig.mutations;
|
||||
}
|
||||
|
||||
async subscribeToVouchers (client: Client): Promise<void> {
|
||||
this.clientAddress = client.address;
|
||||
|
||||
// Load existing open payment channels with amount paid so far from the stored state
|
||||
await this.loadPaymentChannels(client);
|
||||
|
||||
const receivedVouchersChannel = client.receivedVouchers();
|
||||
log('Starting voucher subscription...');
|
||||
|
||||
@ -77,19 +111,27 @@ export class PaymentsManager {
|
||||
|
||||
const associatedPaymentChannel = await client.getPaymentChannel(voucher.channelId);
|
||||
const payer = associatedPaymentChannel.balance.payer;
|
||||
log(`Received a payment voucher from ${payer}`);
|
||||
|
||||
let vouchersMap = this.receivedVouchers.get(payer);
|
||||
if (!vouchersMap) {
|
||||
vouchersMap = new LRUCache<string, Voucher>({
|
||||
max: LRU_CACHE_MAX_VOUCHER_COUNT,
|
||||
ttl: LRU_CACHE_VOUCHER_TTL
|
||||
});
|
||||
|
||||
this.receivedVouchers.set(payer, vouchersMap);
|
||||
if (!voucher.amount) {
|
||||
log(`Amount not set in received voucher on payment channel ${voucher.channelId.string()}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
vouchersMap.set(voucher.hash(), voucher);
|
||||
const paymentAmount = voucher.amount - (this.paidSoFarOnChannel.get(voucher.channelId.string()) ?? BigInt(0));
|
||||
this.paidSoFarOnChannel.set(voucher.channelId.string(), voucher.amount);
|
||||
log(`Received a payment voucher of ${paymentAmount} from ${payer}`);
|
||||
|
||||
let paymentsMap = this.receivedPayments.get(payer);
|
||||
if (!paymentsMap) {
|
||||
paymentsMap = new LRUCache<string, Payment>({
|
||||
max: this.config.cache.maxVouchersPerAccount ?? DEFAULT_LRU_CACHE_MAX_VOUCHERS_PER_ACCOUNT,
|
||||
ttl: (this.config.cache.voucherTTLInSecs ?? DEFAULT_LRU_CACHE_VOUCHER_TTL) * 1000
|
||||
});
|
||||
|
||||
this.receivedPayments.set(payer, paymentsMap);
|
||||
}
|
||||
|
||||
paymentsMap.set(voucher.hash(), { voucher, amount: paymentAmount });
|
||||
|
||||
for await (const [, listener] of this.paymentListeners.entries()) {
|
||||
await listener.push(payer);
|
||||
@ -109,42 +151,51 @@ export class PaymentsManager {
|
||||
await this.stopSubscriptionLoop.close();
|
||||
}
|
||||
|
||||
async allowRequest (voucherHash: string, voucherSig: string): Promise<[boolean, string]> {
|
||||
const senderAddress = nitroUtils.getSignerAddress(voucherHash, voucherSig);
|
||||
async allowRequest (voucherHash: string, voucherSig: string, querySelection: string): Promise<[false, string] | [true, null]> {
|
||||
const signerAddress = nitroUtils.getSignerAddress(voucherHash, voucherSig);
|
||||
|
||||
// Use free quota if EMPTY_VOUCHER_HASH passed
|
||||
if (voucherHash === EMPTY_VOUCHER_HASH) {
|
||||
let remainingFreeQueries = this.remainingFreeQueriesMap.get(senderAddress);
|
||||
let remainingFreeQueries = this.remainingFreeQueriesMap.get(signerAddress);
|
||||
if (remainingFreeQueries === undefined) {
|
||||
remainingFreeQueries = FREE_QUERY_LIMIT;
|
||||
remainingFreeQueries = this.ratesConfig.freeQueriesLimit ?? DEFAULT_FREE_QUERIES_LIMIT;
|
||||
}
|
||||
|
||||
// Check if user has exhausted their free query limit
|
||||
if (remainingFreeQueries > 0) {
|
||||
log(`Serving a free query for ${senderAddress}`);
|
||||
this.remainingFreeQueriesMap.set(senderAddress, remainingFreeQueries - 1);
|
||||
log(`Serving a free query to ${signerAddress}`);
|
||||
this.remainingFreeQueriesMap.set(signerAddress, remainingFreeQueries - 1);
|
||||
|
||||
return [true, ''];
|
||||
return [true, null];
|
||||
}
|
||||
|
||||
log(`Rejecting query from ${senderAddress}, user has exhausted their free quota`);
|
||||
log(`Rejecting query from ${signerAddress}: ${ERR_FREE_QUOTA_EXHUASTED}`);
|
||||
return [false, ERR_FREE_QUOTA_EXHUASTED];
|
||||
}
|
||||
|
||||
// Check for payment voucher received from the Nitro account
|
||||
const paymentVoucherRecived = await this.authenticateVoucher(voucherHash, senderAddress);
|
||||
// Serve a query for free if rate is not configured
|
||||
const configuredQueryCost = this.ratesConfig.queries[querySelection];
|
||||
if (configuredQueryCost === undefined) {
|
||||
log(`Query rate not configured for "${querySelection}", serving a free query to ${signerAddress}`);
|
||||
return [true, null];
|
||||
}
|
||||
|
||||
if (paymentVoucherRecived) {
|
||||
log(`Serving a paid query for ${senderAddress}`);
|
||||
return [true, ''];
|
||||
// Check if required payment received from the Nitro account
|
||||
const [paymentReceived, paymentError] = await this.authenticatePayment(voucherHash, signerAddress, BigInt(configuredQueryCost));
|
||||
|
||||
if (paymentReceived) {
|
||||
log(`Serving a paid query for ${signerAddress}`);
|
||||
return [true, null];
|
||||
} else {
|
||||
log(`Rejecting query from ${senderAddress}, payment voucher not received`);
|
||||
return [false, ERR_PAYMENT_NOT_RECEIVED];
|
||||
log(`Rejecting query from ${signerAddress}: ${paymentError}`);
|
||||
return [false, paymentError];
|
||||
}
|
||||
}
|
||||
|
||||
async authenticateVoucher (voucherHash:string, senderAddress: string): Promise<boolean> {
|
||||
if (this.acceptReceivedVouchers(voucherHash, senderAddress)) {
|
||||
return true;
|
||||
async authenticatePayment (voucherHash:string, signerAddress: string, value: bigint): Promise<[false, string] | [true, null]> {
|
||||
const [isPaymentReceived, isOfSufficientValue] = this.acceptReceivedPayment(voucherHash, signerAddress, value);
|
||||
if (isPaymentReceived) {
|
||||
return isOfSufficientValue ? [true, null] : [false, ERR_AMOUNT_INSUFFICIENT];
|
||||
}
|
||||
|
||||
// Wait for payment voucher from sender
|
||||
@ -153,7 +204,7 @@ export class PaymentsManager {
|
||||
let requestTimeout;
|
||||
|
||||
const timeoutPromise = new Promise(resolve => {
|
||||
requestTimeout = setTimeout(resolve, REQUEST_TIMEOUT);
|
||||
requestTimeout = setTimeout(resolve, (this.config.requestTimeoutInSecs ?? DEFAULT_REQUEST_TIMEOUT) * 1000);
|
||||
});
|
||||
|
||||
try {
|
||||
@ -165,12 +216,13 @@ export class PaymentsManager {
|
||||
|
||||
// payer is undefined if timeout completes or channel is closed externally
|
||||
if (!payer) {
|
||||
return false;
|
||||
return [false, ERR_PAYMENT_NOT_RECEIVED];
|
||||
}
|
||||
|
||||
if (payer === senderAddress) {
|
||||
if (this.acceptReceivedVouchers(voucherHash, senderAddress)) {
|
||||
return true;
|
||||
if (payer === signerAddress) {
|
||||
const [isPaymentReceived, isOfSufficientValue] = this.acceptReceivedPayment(voucherHash, signerAddress, value);
|
||||
if (isPaymentReceived) {
|
||||
return isOfSufficientValue ? [true, null] : [false, ERR_AMOUNT_INSUFFICIENT];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -184,24 +236,43 @@ export class PaymentsManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Check vouchers in LRU cache map and remove them
|
||||
// Returns false if not found
|
||||
// Returns true after being found and removed
|
||||
private acceptReceivedVouchers (voucherHash:string, senderAddress: string): boolean {
|
||||
const vouchersMap = this.receivedVouchers.get(senderAddress);
|
||||
// Check for a given payment voucher in LRU cache map
|
||||
// Returns whether the voucher was found, whether it was of sufficient value
|
||||
private acceptReceivedPayment (voucherHash:string, signerAddress: string, minRequiredValue: bigint): [boolean, boolean] {
|
||||
const paymentsMap = this.receivedPayments.get(signerAddress);
|
||||
|
||||
if (!vouchersMap) {
|
||||
return false;
|
||||
if (!paymentsMap) {
|
||||
return [false, false];
|
||||
}
|
||||
|
||||
const receivedVoucher = vouchersMap.get(voucherHash);
|
||||
const receivedPayment = paymentsMap.get(voucherHash);
|
||||
|
||||
if (!receivedVoucher) {
|
||||
return false;
|
||||
if (!receivedPayment) {
|
||||
return [false, false];
|
||||
}
|
||||
|
||||
vouchersMap.delete(voucherHash);
|
||||
return true;
|
||||
if (receivedPayment.amount < minRequiredValue) {
|
||||
return [true, false];
|
||||
}
|
||||
|
||||
paymentsMap.delete(voucherHash);
|
||||
return [true, true];
|
||||
}
|
||||
|
||||
private async loadPaymentChannels (client: Client): Promise<void> {
|
||||
const ledgerChannels = await client.getAllLedgerChannels();
|
||||
|
||||
for await (const ledgerChannel of ledgerChannels) {
|
||||
if (ledgerChannel.status === ChannelStatus.Open) {
|
||||
const paymentChannels = await client.getPaymentChannelsByLedger(ledgerChannel.iD);
|
||||
|
||||
for (const paymentChannel of paymentChannels) {
|
||||
if (paymentChannel.status === ChannelStatus.Open) {
|
||||
this.paidSoFarOnChannel.set(paymentChannel.iD.string(), paymentChannel.balance.paidSoFar);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,22 +314,21 @@ export const paymentsPlugin = (paymentsManager?: PaymentsManager): ApolloServerP
|
||||
}
|
||||
|
||||
const querySelections = requestContext.operation?.selectionSet.selections
|
||||
.map((selection) => (selection as FieldNode).name.value);
|
||||
.map((selection: any) => (selection as FieldNode).name.value);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
for await (const querySelection of querySelections ?? []) {
|
||||
// TODO: Charge according to the querySelection
|
||||
if (FREE_QUERIES.includes(querySelection)) {
|
||||
if (paymentsManager.freeQueriesList.includes(querySelection)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const [allowRequest, rejectionMessage] = await paymentsManager.allowRequest(vhash, vsig);
|
||||
const [allowRequest, rejectionMessage] = await paymentsManager.allowRequest(vhash, vsig, querySelection);
|
||||
if (!allowRequest) {
|
||||
const failResponse: GraphQLResponse = {
|
||||
errors: [{ message: rejectionMessage }],
|
||||
http: new HTTPResponse(undefined, {
|
||||
headers: requestContext.response?.http?.headers,
|
||||
status: HTTP_CODE_PAYMENT_NOT_RECEIVED
|
||||
status: HTTP_CODE_PAYMENT_REQUIRED
|
||||
})
|
||||
};
|
||||
|
||||
|
21
yarn.lock
21
yarn.lock
@ -350,14 +350,14 @@
|
||||
wherearewe "^2.0.0"
|
||||
xsalsa20 "^1.1.0"
|
||||
|
||||
"@cerc-io/nitro-client@^0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fnitro-client/-/0.1.4/nitro-client-0.1.4.tgz#7f69fa5fa66beb7eedd6dddc81a29d2589e6d60d"
|
||||
integrity sha512-2onCl2wygbXXhtjdMotRdeOXovFeiCegenE/y4uFKxOX83LpslTx/iK+d6HqcQHW4Kkc2pExu6PBntaPoKVpLA==
|
||||
"@cerc-io/nitro-client@^0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fnitro-client/-/0.1.5/nitro-client-0.1.5.tgz#43152a8482b53431c35018064cc70031b759895b"
|
||||
integrity sha512-px/7IgOv1m+DWskJPQ4DUyX84MZHOYMEPN3iNK9uPf+TjyQQm0w2eTPcdQEQN20xzfFFx3k+Dzys3Ko06pXPDQ==
|
||||
dependencies:
|
||||
"@cerc-io/libp2p" "0.42.2-laconic-0.1.3"
|
||||
"@cerc-io/nitro-util" "^0.1.4"
|
||||
"@cerc-io/peer" "^0.2.46"
|
||||
"@cerc-io/nitro-util" "^0.1.5"
|
||||
"@cerc-io/peer" "^0.2.49"
|
||||
"@cerc-io/ts-channel" "1.0.3-ts-nitro-0.1.1"
|
||||
"@libp2p/crypto" "^1.0.4"
|
||||
"@libp2p/tcp" "^6.0.0"
|
||||
@ -373,11 +373,12 @@
|
||||
promjs "^0.4.2"
|
||||
uint8arrays "^4.0.3"
|
||||
|
||||
"@cerc-io/nitro-util@^0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fnitro-util/-/0.1.4/nitro-util-0.1.4.tgz#101f61692050f0ae850b0a0a12e56252f3ddc5a8"
|
||||
integrity sha512-dqzFA5czCxo/yyAhHMplzRzTyzf52WpU8U+ntZ9zFIfrWPHvHHyDY4YyjA2yVQEWfcyyIZ0GTzf6wDb/kP8dKg==
|
||||
"@cerc-io/nitro-util@^0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fnitro-util/-/0.1.5/nitro-util-0.1.5.tgz#829d7cb56c436fcf29184d1d87ed47ac57a2a32f"
|
||||
integrity sha512-1nNXfoHVOV2QSnnQSiW/dgTuMGJYEcN2M12e5rMXAb4KrJIScT1lWTp+P5dja/F6jN8ZuNlu5REQTdOjdhMNwQ==
|
||||
dependencies:
|
||||
"@statechannels/nitro-protocol" "^2.0.0-alpha.4"
|
||||
assert "^2.0.0"
|
||||
debug "^4.3.4"
|
||||
ethers "^5.7.2"
|
||||
|
Loading…
Reference in New Issue
Block a user