Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change local rpc #1101

Merged
merged 3 commits into from
Jul 12, 2023
Merged
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
4 changes: 2 additions & 2 deletions environments/.env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ SERVER_URL=https://staging.sourcify.dev/server

# Custom nodes
NODE_URL_MAINNET=
NODE_URL_RINKEBY=
NODE_URL_GOERLI=
NODE_URL_SEPOLIA=

CF_ACCESS_CLIENT_ID=
CF_ACCESS_CLIENT_SECRET=

# Other config
TESTING=false
Expand Down
9 changes: 5 additions & 4 deletions environments/.env.latest
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ IPFS_API_EXTERNAL_PORT=5002
SERVER_URL=https://staging.sourcify.dev/server

# Custom nodes
NODE_URL_MAINNET=http://10.10.42.102:8541
NODE_URL_RINKEBY=http://10.10.42.102:8544
NODE_URL_GOERLI=http://10.10.42.102:8545
NODE_URL_SEPOLIA=http://10.10.42.102:8546
NODE_URL_MAINNET=https://rpc.mainnet.ethpandaops.io
NODE_URL_GOERLI=https://rpc.goerli.ethpandaops.io
NODE_URL_SEPOLIA=https://rpc.sepolia.ethpandaops.io
CF_ACCESS_CLIENT_ID=xxx
CF_ACCESS_CLIENT_SECRET=xxx

# Other config
TESTING=false
Expand Down
Binary file modified environments/.env.secrets.gpg
Binary file not shown.
10 changes: 5 additions & 5 deletions environments/.env.stable
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ IPFS_API_EXTERNAL_PORT=5003
SERVER_URL=https://sourcify.dev/server

# Custom nodes
NODE_URL_MAINNET=http://10.10.42.102:8541
NODE_URL_RINKEBY=http://10.10.42.102:8544
NODE_URL_GOERLI=http://10.10.42.102:8545
NODE_URL_SEPOLIA=http://10.10.42.102:8546

NODE_URL_MAINNET=https://rpc.mainnet.ethpandaops.io
NODE_URL_GOERLI=https://rpc.goerli.ethpandaops.io
NODE_URL_SEPOLIA=https://rpc.sepolia.ethpandaops.io
CF_ACCESS_CLIENT_ID=xxx
CF_ACCESS_CLIENT_SECRET=xxx

# Other config
TESTING=false
Expand Down
1 change: 1 addition & 0 deletions packages/lib-sourcify/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { setLogger, setLevel, ILogger } from './lib/logger';
export * from './lib/validation';
export * from './lib/verification';
export * from './lib/CheckedContract';
export { default as SourcifyChain } from './lib/SourcifyChain';
export * from './lib/types';
export * from './lib/solidityCompiler';
export const setLibSourcifyLogger = setLogger;
Expand Down
221 changes: 221 additions & 0 deletions packages/lib-sourcify/src/lib/SourcifyChain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import {
FetchRequest,
JsonRpcProvider,
Network,
TransactionResponse,
getAddress,
} from 'ethers';
import { Chain, SourcifyChainExtension } from './types';
import { logInfo, logWarn } from './logger';

const RPC_TIMEOUT = process.env.RPC_TIMEOUT
? parseInt(process.env.RPC_TIMEOUT)
: 5000;

// It is impossible to get the url from the Provider for logging purposes
interface JsonRpcProviderWithUrl extends JsonRpcProvider {
url?: string;
}

// Need to define the rpc property explicitly as when a sourcifyChain is created with {...chain, sourcifyChainExtension}, Typescript throws with "Type '(string | FetchRequest)[]' is not assignable to type 'string[]'." For some reason the Chain.rpc is not getting overwritten by SourcifyChainExtension.rpc
export type SourcifyChainInstance = Omit<Chain, 'rpc'> &
Omit<SourcifyChainExtension, 'rpc'> & { rpc: Array<string | FetchRequest> };

export default class SourcifyChain {
name: string;
title?: string | undefined;
chainId: number;
rpc: Array<string | FetchRequest>;
supported: boolean;
monitored: boolean;
contractFetchAddress?: string | undefined;
graphQLFetchAddress?: string | undefined;
txRegex?: string[] | undefined;
providers: JsonRpcProviderWithUrl[];

constructor(sourcifyChainObj: SourcifyChainInstance) {
this.name = sourcifyChainObj.name;
this.title = sourcifyChainObj.title;
this.chainId = sourcifyChainObj.chainId;
this.rpc = sourcifyChainObj.rpc;
this.supported = sourcifyChainObj.supported;
this.monitored = sourcifyChainObj.monitored;
this.contractFetchAddress = sourcifyChainObj.contractFetchAddress;
this.graphQLFetchAddress = sourcifyChainObj.graphQLFetchAddress;
this.txRegex = sourcifyChainObj.txRegex;
this.providers = [];

if (!this.supported) return; // Don't create providers if chain is not supported

if (!this?.rpc.length)
throw new Error(
'No RPC provider was given for this chain with id ' +
this.chainId +
' and name ' +
this.name
);

for (const rpc of this.rpc) {
let provider: JsonRpcProviderWithUrl | undefined;
const ethersNetwork = new Network(this.name, this.chainId);
if (typeof rpc === 'string') {
if (rpc.startsWith('http')) {
// Use staticNetwork to avoid sending unnecessary eth_chainId requests
provider = new JsonRpcProvider(rpc, ethersNetwork, {
staticNetwork: ethersNetwork,
});
provider.url = rpc;
} else {
// Do not use WebSockets because of not being able to catch errors on websocket initialization. Most networks don't support WebSockets anyway. See https://github.com/ethers-io/ethers.js/discussions/2896
// provider = new WebSocketProvider(rpc);
logInfo(`Won't create a WebSocketProvider for ${rpc}`);
}
} else {
provider = new JsonRpcProvider(rpc, ethersNetwork, {
staticNetwork: ethersNetwork,
});
provider.url = rpc.url;
}
if (provider) {
this.providers.push(provider);
}
}
}

rejectInMs = (ms: number, host?: string) =>
new Promise<never>((_resolve, reject) => {
setTimeout(() => reject(`RPC ${host} took too long to respond`), ms);
});

getTx = async (creatorTxHash: string) => {
// Try sequentially all providers
for (const provider of this.providers) {
try {
// Race the RPC call with a timeout
const tx = await Promise.race([
provider.getTransaction(creatorTxHash),
this.rejectInMs(RPC_TIMEOUT, provider.url),
]);
if (tx instanceof TransactionResponse) {
logInfo(
`Transaction ${creatorTxHash} fetched via ${provider.url} from chain ${this.chainId}`
);
return tx;
} else {
throw new Error(
`Transaction ${creatorTxHash} not found on RPC ${provider.url} and chain ${this.chainId}`
);
}
} catch (err) {
if (err instanceof Error) {
logWarn(
`Can't fetch the transaction ${creatorTxHash} from RPC ${provider.url} and chain ${this.chainId}\n ${err}`
);
continue;
} else {
throw err;
}
}
}
throw new Error(
'None of the RPCs responded fetching tx ' +
creatorTxHash +
' on chain ' +
this.chainId
);
};

/**
* Fetches the contract's deployed bytecode from SourcifyChain's rpc's.
* Tries to fetch sequentially if the first RPC is a local eth node. Fetches in parallel otherwise.
*
* @param {SourcifyChain} sourcifyChain - chain object with rpc's
* @param {string} address - contract address
*/
getBytecode = async (address: string): Promise<string> => {
address = getAddress(address);

// Request sequentially. Custom node is always before ALCHEMY so we don't waste resources if succeeds.
for (const provider of this.providers) {
try {
// Race the RPC call with a timeout
const bytecode = await Promise.race([
provider.getCode(address),
this.rejectInMs(RPC_TIMEOUT, provider.url),
]);
return bytecode;
} catch (err) {
if (err instanceof Error) {
logWarn(
`Can't fetch bytecode from RPC ${provider.url} and chain ${this.chainId}`
);
continue;
} else {
throw err;
}
}
}
throw new Error(
'None of the RPCs responded fetching bytecode for ' +
address +
' on chain ' +
this.chainId
);
};

getBlock = async (blockNumber: number, preFetchTxs = true) => {
// Request sequentially. Custom node is always before ALCHEMY so we don't waste resources if succeeds.
for (const provider of this.providers) {
try {
// Race the RPC call with a timeout
const block = await Promise.race([
provider.getBlock(blockNumber, preFetchTxs),
this.rejectInMs(RPC_TIMEOUT, provider.url),
]);
return block;
} catch (err) {
if (err instanceof Error) {
logWarn(
`Can't fetch block ${blockNumber} from RPC ${provider.url} and chain ${this.chainId}`
);
continue;
} else {
throw err;
}
}
}
throw new Error(
'None of the RPCs responded fetching block ' +
blockNumber +
' on chain ' +
this.chainId
);
};

getBlockNumber = async () => {
// Request sequentially. Custom node is always before ALCHEMY so we don't waste resources if succeeds.
for (const provider of this.providers) {
try {
// Race the RPC call with a timeout
const block = await Promise.race([
provider.getBlockNumber(),
this.rejectInMs(RPC_TIMEOUT, provider.url),
]);
return block;
} catch (err) {
if (err instanceof Error) {
logWarn(
`Can't fetch the current block number from RPC ${provider.url} and chain ${this.chainId}`
);
continue;
} else {
throw err;
}
}
}
throw new Error(
'None of the RPCs responded fetching the blocknumber on chain ' +
this.chainId
);
};
}
10 changes: 4 additions & 6 deletions packages/lib-sourcify/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Abi } from 'abitype';

import { FetchRequest } from 'ethers';
import SourcifyChain from './SourcifyChain';
export interface PathBuffer {
path: string;
buffer: Buffer;
Expand Down Expand Up @@ -190,7 +191,7 @@ export type SourcifyChainExtension = {
contractFetchAddress?: string;
graphQLFetchAddress?: string;
txRegex?: string[];
rpc?: string[];
rpc?: Array<string | FetchRequest>;
};

// TODO: Double check against ethereum-lists/chains type
Expand All @@ -202,14 +203,11 @@ export type Chain = {
network?: string;
networkId: number;
nativeCurrency: Currency;
rpc: string[];
rpc: Array<string>;
faucets?: string[];
infoURL?: string;
};

// a type that extends the Chain type
export type SourcifyChain = Chain & SourcifyChainExtension;

export type SourcifyChainMap = {
[chainId: string]: SourcifyChain;
};
Expand Down
Loading