From 8e587cd53a99768ef8f59acb421982f8151f9077 Mon Sep 17 00:00:00 2001 From: prathamesh0 <42446521+prathamesh0@users.noreply.github.com> Date: Tue, 3 Oct 2023 10:19:21 +0530 Subject: [PATCH] Pay upstream ETH provider for RPC requests (#25) * 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 --- environments/local.toml | 8 +++- package.json | 12 ++--- src/indexer.ts | 3 +- src/libp2p-utils.ts | 2 +- src/payment-utils.ts | 97 +++++++++++++++++++++++++++++++++++++++++ src/server.ts | 23 ++++++++-- yarn.lock | 80 ++++++++++++++++----------------- 7 files changed, 172 insertions(+), 53 deletions(-) create mode 100644 src/payment-utils.ts diff --git a/environments/local.toml b/environments/local.toml index 8bf80c6..3655b00 100644 --- a/environments/local.toml +++ b/environments/local.toml @@ -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" diff --git a/package.json b/package.json index cfce235..504247d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/indexer.ts b/src/indexer.ts index fc46ec1..68e5e1b 100644 --- a/src/indexer.ts +++ b/src/indexer.ts @@ -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, diff --git a/src/libp2p-utils.ts b/src/libp2p-utils.ts index 2ad9536..8980110 100644 --- a/src/libp2p-utils.ts +++ b/src/libp2p-utils.ts @@ -51,7 +51,7 @@ export async function handlePayment ( payment: { vhash: string, vsig: string }, requestKind: string ): Promise { - assert(paymentsManager.clientAddress); + assert(paymentsManager.nitro); // Retrieve sender address const signerAddress = nitroUtils.getSignerAddress(payment.vhash, payment.vsig); diff --git a/src/payment-utils.ts b/src/payment-utils.ts new file mode 100644 index 0000000..12165f3 --- /dev/null +++ b/src/payment-utils.ts @@ -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 { + // 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): Promise => { + 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; +} diff --git a/src/server.ts b/src/server.ts index 16067fc..448529b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -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'; @@ -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'); @@ -46,6 +47,7 @@ export const main = async (): Promise => { 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); @@ -53,10 +55,18 @@ export const main = async (): Promise => { // 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; @@ -64,7 +74,12 @@ export const main = async (): Promise => { // 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); } diff --git a/yarn.lock b/yarn.lock index 633b1b1..884cb8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -162,10 +162,10 @@ 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" @@ -173,19 +173,19 @@ 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" @@ -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" @@ -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" @@ -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" @@ -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" @@ -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==