Skip to content

Commit

Permalink
Pay upstream ETH provider for RPC requests (#25)
Browse files Browse the repository at this point in the history
* Setup a separate upstream RPC provider for mutation requests

* Setup a payment channel with the upstream Nitro node

* Override ethersjs provider to send payment with each request

* Use latest block tag for latestBlock query

* Only pay for selective RPC requests

* Upgrade cerc-io packages

* Check for Nitro node address before setting up the payment channel
  • Loading branch information
prathamesh0 committed Oct 3, 2023
1 parent 9f7d105 commit 8e587cd
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 53 deletions.
8 changes: 7 additions & 1 deletion environments/local.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,13 @@
[upstream]
[upstream.ethServer]
gqlApiEndpoint = "http://127.0.0.1:8082/graphql"
rpcProviderEndpoint = "http://127.0.0.1:8545"
rpcProviderEndpoint = "http://127.0.0.1:8081"
rpcProviderMutationEndpoint = "http://127.0.0.1:8545"

[upstream.ethServer.rpcProviderNitroNode]
address = ""
multiAddr = ""
amount = "5000"

[upstream.cache]
name = "requests"
Expand Down
12 changes: 7 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@
"homepage": "https://github.com/cerc-io/mobymask-v2-watcher-ts#readme",
"dependencies": {
"@apollo/client": "^3.3.19",
"@cerc-io/peer": "^0.2.59",
"@cerc-io/cli": "^0.2.59",
"@cerc-io/ipld-eth-client": "^0.2.59",
"@cerc-io/cli": "^0.2.61",
"@cerc-io/ipld-eth-client": "^0.2.61",
"@cerc-io/nitro-node": "^0.1.11",
"@cerc-io/solidity-mapper": "^0.2.59",
"@cerc-io/util": "^0.2.59",
"@cerc-io/peer": "^0.2.61",
"@cerc-io/solidity-mapper": "^0.2.61",
"@cerc-io/util": "^0.2.61",
"@ethersproject/properties": "^5.7.0",
"@ethersproject/providers": "^5.4.4",
"@ethersproject/web": "^5.7.1",
"apollo-type-bigint": "^0.1.3",
"debug": "^4.3.1",
"decimal.js": "^10.3.1",
Expand Down
3 changes: 1 addition & 2 deletions src/indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -659,8 +659,7 @@ export class Indexer implements IndexerInterface {
// const { block } = await this._ethClient.getBlockByHash();

// Use ETH RPC endpoint
const number = await this._ethProvider.getBlockNumber();
const { hash } = await this._ethProvider.getBlock(number);
const { number, hash } = await this._ethProvider.getBlock('latest');

return {
number,
Expand Down
2 changes: 1 addition & 1 deletion src/libp2p-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export async function handlePayment (
payment: { vhash: string, vsig: string },
requestKind: string
): Promise<boolean> {
assert(paymentsManager.clientAddress);
assert(paymentsManager.nitro);

// Retrieve sender address
const signerAddress = nitroUtils.getSignerAddress(payment.vhash, payment.vsig);
Expand Down
97 changes: 97 additions & 0 deletions src/payment-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// Copyright 2023 Vulcanize, Inc.
//

import debug from 'debug';
import { providers } from 'ethers';

import { PaymentsManager } from '@cerc-io/util';
import { deepCopy } from '@ethersproject/properties';
import { fetchJson } from '@ethersproject/web';

const log = debug('vulcanize:payment-utils');

// TODO: Configure
const paidRPCMethods = [
'eth_getBlockByHash',
'eth_getStorageAt'
];

export async function setupProviderWithPayments (provider: providers.JsonRpcProvider, paymentsManager: PaymentsManager, paymentAmount: string): Promise<void> {
// https://github.com/ethers-io/ethers.js/blob/v5.7.2/packages/providers/src.ts/json-rpc-provider.ts#L502
provider.send = async (method: string, params: Array<any>): Promise<any> => {
log(`Making RPC call: ${method}`);

const request = {
method: method,
params: params,
id: (provider._nextId++),
jsonrpc: '2.0'
};

provider.emit('debug', {
action: 'request',
request: deepCopy(request),
provider: provider
});

// We can expand this in the future to any call, but for now these
// are the biggest wins and do not require any serializing parameters.
const cache = (['eth_chainId', 'eth_blockNumber'].indexOf(method) >= 0);
// @ts-expect-error copied code
if (cache && provider._cache[method]) {
return provider._cache[method];
}

// Send a payment to upstream Nitro node and add details to the request URL
let updatedURL = `${provider.connection.url}?method=${method}`;
if (paidRPCMethods.includes(method)) {
const voucher = await paymentsManager.sendUpstreamPayment(paymentAmount);
updatedURL = `${updatedURL}&channelId=${voucher.channelId}&amount=${voucher.amount}&signature=${voucher.signature}`;
}

const result = fetchJson({ ...provider.connection, url: updatedURL }, JSON.stringify(request), getResult).then((result) => {
provider.emit('debug', {
action: 'response',
request: request,
response: result,
provider: provider
});

return result;
}, (error) => {
provider.emit('debug', {
action: 'response',
error: error,
request: request,
provider: provider
});

throw error;
});

// Cache the fetch, but clear it on the next event loop
if (cache) {
provider._cache[method] = result;
setTimeout(() => {
// @ts-expect-error copied code
provider._cache[method] = null;
}, 0);
}

return result;
};
}

// https://github.com/ethers-io/ethers.js/blob/v5.7.2/packages/providers/src.ts/json-rpc-provider.ts#L139
function getResult (payload: { error?: { code?: number, data?: any, message?: string }, result?: any }): any {
if (payload.error) {
// @TODO: not any
const error: any = new Error(payload.error.message);
error.code = payload.error.code;
error.data = payload.error.data;
throw error;
}

return payload.result;
}
23 changes: 19 additions & 4 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import path from 'path';
import assert from 'assert';
import 'reflect-metadata';
import debug from 'debug';
import { ethers } from 'ethers';
import { ethers, providers } from 'ethers';

import { ServerCmd } from '@cerc-io/cli';

Expand All @@ -23,6 +23,7 @@ import {
import { PaymentsManager, getConfig } from '@cerc-io/util';

import { RatesConfig } from './config';
import { setupProviderWithPayments } from './payment-utils';

const log = debug('vulcanize:server');

Expand All @@ -46,25 +47,39 @@ export const main = async (): Promise<any> => {

let nitroPaymentsManager: PaymentsManager | undefined;
const { enablePeer, peer: { enableL2Txs, l2TxsConfig, pubSubTopic }, nitro: { payments } } = serverCmd.config.server.p2p;
const { rpcProviderMutationEndpoint, rpcProviderNitroNode } = serverCmd.config.upstream.ethServer;

if (enablePeer) {
assert(peer);
assert(nitro);

// Setup the payments manager if peer is enabled
const ratesConfig: RatesConfig = await getConfig(payments.ratesFile);
nitroPaymentsManager = new PaymentsManager(payments, ratesConfig);
nitroPaymentsManager = new PaymentsManager(nitro, payments, ratesConfig);

// Start subscription for payment vouchers received by the client
nitroPaymentsManager.subscribeToVouchers(nitro);
nitroPaymentsManager.subscribeToVouchers();

// Setup a payment channel with the upstream Nitro node if provided in config
// Setup the provider to send payment with each request
if (rpcProviderNitroNode?.address) {
nitroPaymentsManager.setupUpstreamPaymentChannel(rpcProviderNitroNode);

setupProviderWithPayments(serverCmd.ethProvider, nitroPaymentsManager, rpcProviderNitroNode.amount);
}

// Register the pubsub topic handler
let p2pMessageHandler = parseLibp2pMessage;

// Send L2 txs for messages if enabled
if (enableL2Txs) {
assert(l2TxsConfig);
const wallet = new ethers.Wallet(l2TxsConfig.privateKey, serverCmd.ethProvider);
// Create a separate provider for mutation requests if rpcProviderMutationEndpoint is provided
const mutationProvider = rpcProviderMutationEndpoint
? new providers.JsonRpcProvider(rpcProviderMutationEndpoint)
: serverCmd.ethProvider;
const wallet = new ethers.Wallet(l2TxsConfig.privateKey, mutationProvider);

p2pMessageHandler = createMessageToL2Handler(wallet, l2TxsConfig, nitroPaymentsManager, consensus);
}

Expand Down
80 changes: 40 additions & 40 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -162,30 +162,30 @@
dependencies:
xss "^1.0.8"

"@cerc-io/cache@^0.2.59":
version "0.2.59"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fcache/-/0.2.59/cache-0.2.59.tgz#526b6f1102c66bf11ee66a02947fd0a0aae19abf"
integrity sha512-/5zBOCqGWGmWBJMu56+FWh6EjatkZqLs1cj3g3OxFKpaBelS5FHGSO870MWc57klD70slkVwARNdlea77JdnLA==
"@cerc-io/cache@^0.2.61":
version "0.2.61"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fcache/-/0.2.61/cache-0.2.61.tgz#75cf6273f45463e381418ab330b86b80df68ce3e"
integrity sha512-AwmCrKE4lhIdVxPXmoS/kFRCrpjlNz/7gJRBYOn74OljQ3hIPoJapYR4E7Nla0AYKPn5o6276QZFuiMCFSx2PQ==
dependencies:
canonical-json "^0.0.4"
debug "^4.3.1"
ethers "^5.4.4"
fs-extra "^10.0.0"
level "^7.0.0"

"@cerc-io/cli@^0.2.59":
version "0.2.59"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fcli/-/0.2.59/cli-0.2.59.tgz#0fcd1f3c14392d3085bf6f1aaffbc929c932f6ed"
integrity sha512-hDjj5xol5JIq2LzRs+DRf6P9OQi+eyl3Qe3R/DFmRtFHBq7gFe6ylBnAbIhw2qfHNUhKA64e8lGMZoTI27Ocyg==
"@cerc-io/cli@^0.2.61":
version "0.2.61"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fcli/-/0.2.61/cli-0.2.61.tgz#fd975854146da2458905c15df831324f65793840"
integrity sha512-WySqgoGsYukfQraJbvBKnurpQg2wOjW+sooE3A1yCGzkMvFw1T0h2qGKnm4YzIafH60zWKM759deyWCqiKaolw==
dependencies:
"@apollo/client" "^3.7.1"
"@cerc-io/cache" "^0.2.59"
"@cerc-io/ipld-eth-client" "^0.2.59"
"@cerc-io/cache" "^0.2.61"
"@cerc-io/ipld-eth-client" "^0.2.61"
"@cerc-io/libp2p" "^0.42.2-laconic-0.1.4"
"@cerc-io/nitro-node" "^0.1.11"
"@cerc-io/peer" "^0.2.59"
"@cerc-io/rpc-eth-client" "^0.2.59"
"@cerc-io/util" "^0.2.59"
"@cerc-io/peer" "^0.2.61"
"@cerc-io/rpc-eth-client" "^0.2.61"
"@cerc-io/util" "^0.2.61"
"@ethersproject/providers" "^5.4.4"
"@graphql-tools/utils" "^9.1.1"
"@ipld/dag-cbor" "^8.0.0"
Expand All @@ -201,13 +201,13 @@
typeorm "0.2.37"
yargs "^17.0.1"

"@cerc-io/ipld-eth-client@^0.2.59":
version "0.2.59"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fipld-eth-client/-/0.2.59/ipld-eth-client-0.2.59.tgz#421c930a528fdc75411ef50954f89b61dbe6c8ba"
integrity sha512-pLw0YXiR4UCV2jekX+Ds97Ug+OjdrmxVaMT6K7E9ejYOjcIOouQ6mebUA+euZrEBaH9V3twaYUo1J0YYjvGtRg==
"@cerc-io/ipld-eth-client@^0.2.61":
version "0.2.61"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fipld-eth-client/-/0.2.61/ipld-eth-client-0.2.61.tgz#1c080acd9da700de669224e02d3004dea18ad4f7"
integrity sha512-SEGjNiczGQkts+C+8m0ZmyRyrCVQvMpJ6EXiEi7hZQQsi9LQXb2DVMo3tQptZ27Nw2u0J9jNQn4Cdw7jIO1nuQ==
dependencies:
"@apollo/client" "^3.7.1"
"@cerc-io/cache" "^0.2.59"
"@cerc-io/cache" "^0.2.61"
cross-fetch "^3.1.4"
debug "^4.3.1"
ethers "^5.4.4"
Expand Down Expand Up @@ -369,10 +369,10 @@
unique-names-generator "^4.7.1"
yargs "^17.0.1"

"@cerc-io/peer@^0.2.59":
version "0.2.59"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fpeer/-/0.2.59/peer-0.2.59.tgz#94ec7a1a04302086cbe165567519939dde6d703d"
integrity sha512-h6rjTkLgQYTDiTU2rOp9VJAiRTf/jU630gHAKhNSft8pla8/rkjhF2hHuOrahXZpHDaQ+etoCOenky2vVfZjdg==
"@cerc-io/peer@^0.2.61":
version "0.2.61"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fpeer/-/0.2.61/peer-0.2.61.tgz#cf2ae86a4968b7c2288acdda866680ff2f3630fe"
integrity sha512-8Uo2JiOferVlSpZ2SUzFIrYLuTr7P41USbb82YL4bJXEUlYd4UqRamN2qTzii8auUhFHlz6el3m+Em6ctxGrlA==
dependencies:
"@cerc-io/libp2p" "^0.42.2-laconic-0.1.4"
"@cerc-io/prometheus-metrics" "1.1.4"
Expand Down Expand Up @@ -411,23 +411,23 @@
it-stream-types "^1.0.4"
promjs "^0.4.2"

"@cerc-io/rpc-eth-client@^0.2.59":
version "0.2.59"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Frpc-eth-client/-/0.2.59/rpc-eth-client-0.2.59.tgz#f064b402b0031abeebb434fbdced585019cb3ba7"
integrity sha512-QhvVrQKUGSYhqhlgSkr8Iz5B0PS8n1xvM1B5f9rSAyGjbgJEOmPQTMKWL91CTwnACMSvDQYxsnQlXL7VCm76Bw==
"@cerc-io/rpc-eth-client@^0.2.61":
version "0.2.61"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Frpc-eth-client/-/0.2.61/rpc-eth-client-0.2.61.tgz#a2d8bcd846c02f4e613767b8490b57d4dde4ca20"
integrity sha512-wZ0dxXDrMie/5zU2q6h2j7C79cNnP1B3JK6vLsQbt9O0AkGMezbDZcJ28JFvTN4Dv1xMOy/xu7wZa+80ac+bXw==
dependencies:
"@cerc-io/cache" "^0.2.59"
"@cerc-io/ipld-eth-client" "^0.2.59"
"@cerc-io/util" "^0.2.59"
"@cerc-io/cache" "^0.2.61"
"@cerc-io/ipld-eth-client" "^0.2.61"
"@cerc-io/util" "^0.2.61"
chai "^4.3.4"
ethers "^5.4.4"
left-pad "^1.3.0"
mocha "^8.4.0"

"@cerc-io/solidity-mapper@^0.2.59":
version "0.2.59"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fsolidity-mapper/-/0.2.59/solidity-mapper-0.2.59.tgz#b5684782354b43058b3f99af47d7656f14ecee00"
integrity sha512-krBy70dvCSmkb6gz2sKjAVDmQvajnUj3QVa4zd5TVrblVru5aGMrHqhM0z7IRnuMaIJI5xll7xr5iZt/xCTD5w==
"@cerc-io/solidity-mapper@^0.2.61":
version "0.2.61"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fsolidity-mapper/-/0.2.61/solidity-mapper-0.2.61.tgz#1175c6df4404792f2304b7fdac8eddbddd789726"
integrity sha512-Xludn9myIHMrolWYIlYO8ZbOjRWX98qV2JaZwYikG/aSkQa8EFvAyQ4Lgeq1Yrw6rRSf4N2SWs/sjCvTeIYT1g==
dependencies:
dotenv "^10.0.0"

Expand All @@ -436,15 +436,15 @@
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fts-channel/-/1.0.3-ts-nitro-0.1.1/ts-channel-1.0.3-ts-nitro-0.1.1.tgz#0768781313a167295c0bf21307f47e02dc17e936"
integrity sha512-2jFICUSyffuZ+8+qRhXuLSJq4GJ6Y02wxiXoubH0Kzv2lIKkJtWICY1ZQQhtXAvP0ncAQB85WJHqtqwH8l7J3Q==

"@cerc-io/util@^0.2.59":
version "0.2.59"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Futil/-/0.2.59/util-0.2.59.tgz#70ef0bbf32b0811f1450f5de2798922958eb4706"
integrity sha512-mClHAjDUza8HMcdGP8pIvm34q+zOIZEjo1S9mxQ5UYls2qJ86LL8m7eQB9mUxWs/rZlJ0tilMv/+cTe1AiKPpQ==
"@cerc-io/util@^0.2.61":
version "0.2.61"
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Futil/-/0.2.61/util-0.2.61.tgz#9bb5bf0693d1299c6ccf588d2fd452adf0f8a11a"
integrity sha512-UjUIPlTcB8R0VAv+RaQRv7QH+yL9EJV29KwVpZmCKIoLQwci+vCtkwdR3w0ZIKslUs6xfMKr4sq8ub6OYyKdDg==
dependencies:
"@apollo/utils.keyvaluecache" "^1.0.1"
"@cerc-io/nitro-node" "^0.1.11"
"@cerc-io/peer" "^0.2.59"
"@cerc-io/solidity-mapper" "^0.2.59"
"@cerc-io/peer" "^0.2.61"
"@cerc-io/solidity-mapper" "^0.2.61"
"@cerc-io/ts-channel" "1.0.3-ts-nitro-0.1.1"
"@ethersproject/providers" "^5.4.4"
"@graphql-tools/schema" "^9.0.10"
Expand Down Expand Up @@ -926,7 +926,7 @@
"@ethersproject/transactions" "^5.7.0"
"@ethersproject/wordlists" "^5.7.0"

"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0":
"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0", "@ethersproject/web@^5.7.1":
version "5.7.1"
resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae"
integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==
Expand Down

0 comments on commit 8e587cd

Please sign in to comment.