diff --git a/environments/.env.dev b/environments/.env.dev index 431657424..d3980b1f2 100644 --- a/environments/.env.dev +++ b/environments/.env.dev @@ -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 diff --git a/environments/.env.latest b/environments/.env.latest index 329ddd605..f50c69c25 100644 --- a/environments/.env.latest +++ b/environments/.env.latest @@ -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 diff --git a/environments/.env.secrets.gpg b/environments/.env.secrets.gpg index 6fcb7484e..8d3c645df 100644 Binary files a/environments/.env.secrets.gpg and b/environments/.env.secrets.gpg differ diff --git a/environments/.env.stable b/environments/.env.stable index 5cafa90a1..8019a3f8b 100644 --- a/environments/.env.stable +++ b/environments/.env.stable @@ -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 diff --git a/packages/lib-sourcify/src/index.ts b/packages/lib-sourcify/src/index.ts index 7a7c0fa6e..2c20204cd 100644 --- a/packages/lib-sourcify/src/index.ts +++ b/packages/lib-sourcify/src/index.ts @@ -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; diff --git a/packages/lib-sourcify/src/lib/CheckedContract.ts b/packages/lib-sourcify/src/lib/CheckedContract.ts index a0ded8143..00e9ce7d7 100644 --- a/packages/lib-sourcify/src/lib/CheckedContract.ts +++ b/packages/lib-sourcify/src/lib/CheckedContract.ts @@ -114,7 +114,13 @@ export class CheckedContract { async tryToFindPerfectMetadata( deployedBytecode: string ): Promise { - const decodedAuxdata = decodeBytecode(deployedBytecode); + let decodedAuxdata; + try { + decodedAuxdata = decodeBytecode(deployedBytecode); + } catch (err) { + // There is no auxdata at all in this contract + return null; + } const pathContent: PathContent[] = Object.keys(this.solidity).map( (path) => { diff --git a/packages/lib-sourcify/src/lib/SourcifyChain.ts b/packages/lib-sourcify/src/lib/SourcifyChain.ts new file mode 100644 index 000000000..9f6f32615 --- /dev/null +++ b/packages/lib-sourcify/src/lib/SourcifyChain.ts @@ -0,0 +1,243 @@ +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 & + Omit & { rpc: Array }; + +export default class SourcifyChain { + name: string; + title?: string | undefined; + chainId: number; + rpc: Array; + 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((_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 => { + 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), + ]); + logInfo( + 'Bytecode fetched from ' + + provider.url + + ' for ' + + address + + ' on chain ' + + this.chainId + ); + 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), + ]); + logInfo( + 'Block fetched from ' + + provider.url + + ' for ' + + blockNumber + + ' on chain ' + + this.chainId + ); + 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), + ]); + logInfo( + 'Block number fetched from ' + + provider.url + + ' on chain ' + + this.chainId + ); + 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 + ); + }; +} diff --git a/packages/lib-sourcify/src/lib/types.ts b/packages/lib-sourcify/src/lib/types.ts index 0bddbf5b8..a0c220bdc 100644 --- a/packages/lib-sourcify/src/lib/types.ts +++ b/packages/lib-sourcify/src/lib/types.ts @@ -1,5 +1,6 @@ import { Abi } from 'abitype'; - +import { FetchRequest } from 'ethers'; +import SourcifyChain from './SourcifyChain'; export interface PathBuffer { path: string; buffer: Buffer; @@ -190,7 +191,7 @@ export type SourcifyChainExtension = { contractFetchAddress?: string; graphQLFetchAddress?: string; txRegex?: string[]; - rpc?: string[]; + rpc?: Array; }; // TODO: Double check against ethereum-lists/chains type @@ -202,14 +203,11 @@ export type Chain = { network?: string; networkId: number; nativeCurrency: Currency; - rpc: string[]; + rpc: Array; faucets?: string[]; infoURL?: string; }; -// a type that extends the Chain type -export type SourcifyChain = Chain & SourcifyChainExtension; - export type SourcifyChainMap = { [chainId: string]: SourcifyChain; }; diff --git a/packages/lib-sourcify/src/lib/verification.ts b/packages/lib-sourcify/src/lib/verification.ts index 1074e254f..31e2dfe11 100644 --- a/packages/lib-sourcify/src/lib/verification.ts +++ b/packages/lib-sourcify/src/lib/verification.ts @@ -6,22 +6,13 @@ import { Match, Metadata, RecompilationResult, - SourcifyChain, StringMap, } from './types'; import { decode as bytecodeDecode, splitAuxdata, } from '@ethereum-sourcify/bytecode-utils'; -import { - JsonRpcProvider, - Network, - TransactionResponse, - WebSocketProvider, - getAddress, - getCreateAddress, - keccak256, -} from 'ethers'; +import { getAddress, getCreateAddress, keccak256 } from 'ethers'; /* import { EVM } from '@ethereumjs/evm'; import { EEI } from '@ethereumjs/vm'; @@ -35,11 +26,8 @@ import { BigNumber } from '@ethersproject/bignumber'; import semverSatisfies from 'semver/functions/satisfies'; import { defaultAbiCoder as abiCoder, ParamType } from '@ethersproject/abi'; import { AbiConstructor } from 'abitype'; -import { logInfo, logWarn } from './logger'; - -const RPC_TIMEOUT = process.env.RPC_TIMEOUT - ? parseInt(process.env.RPC_TIMEOUT) - : 5000; +import { logInfo } from './logger'; +import SourcifyChain from './SourcifyChain'; export async function verifyDeployed( checkedContract: CheckedContract, @@ -69,7 +57,7 @@ export async function verifyDeployed( ); } - const deployedBytecode = await getBytecode(sourcifyChain, address); + const deployedBytecode = await sourcifyChain.getBytecode(address); // Can't match if there is no deployed bytecode if (!deployedBytecode) { @@ -394,7 +382,7 @@ export async function matchWithCreationTx( return; } - const creatorTx = await getTx(creatorTxHash, sourcifyChain); + const creatorTx = await sourcifyChain.getTx(creatorTxHash); const creatorTxData = creatorTx.data; // The reason why this uses `startsWith` instead of `===` is that creationTxData may contain constructor arguments at the end part. @@ -474,107 +462,6 @@ export async function matchWithCreationTx( match.creatorTxHash = creatorTxHash; } } -/** - * 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 - */ -export async function getBytecode( - sourcifyChain: SourcifyChain, - address: string -): Promise { - if (!sourcifyChain?.rpc.length) - throw new Error('No RPC provider was given for this chain.'); - address = getAddress(address); - - // Request sequentially. Custom node is always before ALCHEMY so we don't waste resources if succeeds. - for (const rpcURL of sourcifyChain.rpc) { - const ethersNetwork = new Network( - sourcifyChain.name, - sourcifyChain.chainId - ); - - const provider = rpcURL.startsWith('http') - ? // Use staticNetwork to avoid seding unnecessary eth_chainId requests - new JsonRpcProvider(rpcURL, ethersNetwork, { - staticNetwork: ethersNetwork, - }) - : new WebSocketProvider(rpcURL); - - try { - // Race the RPC call with a timeout - const bytecode = await Promise.race([ - provider.getCode(address), - rejectInMs(RPC_TIMEOUT, rpcURL), - ]); - return bytecode; - } catch (err) { - if (err instanceof Error) { - logWarn( - `Can't fetch bytecode from RPC ${rpcURL} and chain ${sourcifyChain.chainId}` - ); - continue; - } else { - throw err; - } - } - } - throw new Error('None of the RPCs responded'); -} - -async function getTx(creatorTxHash: string, sourcifyChain: SourcifyChain) { - if (!sourcifyChain?.rpc.length) - throw new Error('No RPC provider was given for this chain.'); - - for (const rpcURL of sourcifyChain.rpc) { - const ethersNetwork = new Network( - sourcifyChain.name, - sourcifyChain.chainId - ); - - const provider = rpcURL.startsWith('http') - ? // Use staticNetwork to avoid seding unnecessary eth_chainId requests - new JsonRpcProvider(rpcURL, ethersNetwork, { - staticNetwork: ethersNetwork, - }) - : new WebSocketProvider(rpcURL); - - try { - // Race the RPC call with a timeout - const tx = await Promise.race([ - provider.getTransaction(creatorTxHash), - rejectInMs(RPC_TIMEOUT, rpcURL), - ]); - if (tx instanceof TransactionResponse) { - logInfo( - `Transaction ${creatorTxHash} fetched via ${rpcURL} from chain ${sourcifyChain.chainId}` - ); - return tx; - } else { - throw new Error( - `Transaction ${creatorTxHash} not found on RPC ${rpcURL} and chain ${sourcifyChain.chainId}` - ); - } - } catch (err) { - if (err instanceof Error) { - logWarn( - `Can't fetch bytecode from RPC ${rpcURL} and chain ${sourcifyChain.chainId}` - ); - continue; - } else { - throw err; - } - } - } - throw new Error('None of the RPCs responded'); -} - -const rejectInMs = (ms: number, host: string) => - new Promise((_resolve, reject) => { - setTimeout(() => reject(`RPC ${host} took too long to respond`), ms); - }); export function addLibraryAddresses( template: string, diff --git a/packages/lib-sourcify/test/verification.spec.ts b/packages/lib-sourcify/test/verification.spec.ts index 8ac17b46c..d2401f97e 100644 --- a/packages/lib-sourcify/test/verification.spec.ts +++ b/packages/lib-sourcify/test/verification.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import path from 'path'; -import { Metadata, SourcifyChain } from '../src/lib/types'; +import { Metadata } from '../src/lib/types'; import Ganache from 'ganache'; import { /* callContractMethodWithTx, */ @@ -13,6 +13,7 @@ import { import { describe, it, before } from 'mocha'; import { expect } from 'chai'; import { + SourcifyChain, calculateCreate2Address, /* getBytecode, @@ -24,8 +25,7 @@ import { verifyDeployed, } from '../src'; import fs from 'fs'; -import { JsonRpcProvider, JsonRpcSigner, Network } from 'ethers'; -// import { Match } from '@ethereum-sourcify/lib-sourcify'; +import { JsonRpcSigner } from 'ethers'; const ganacheServer = Ganache.server({ wallet: { totalAccounts: 1 }, @@ -35,7 +35,7 @@ const GANACHE_PORT = 8545; const UNUSED_ADDRESS = '0x1F98431c8aD98523631AE4a59f267346ea31F984'; // checksum valid -const sourcifyChainGanache: SourcifyChain = { +const ganacheChain = { name: 'ganache', shortName: 'ganache', chainId: 0, @@ -49,23 +49,14 @@ const sourcifyChainGanache: SourcifyChain = { monitored: false, supported: true, }; +const sourcifyChainGanache: SourcifyChain = new SourcifyChain(ganacheChain); -let localProvider: JsonRpcProvider; let signer: JsonRpcSigner; describe('lib-sourcify tests', () => { before(async () => { await ganacheServer.listen(GANACHE_PORT); - const ethersNetwork = new Network( - sourcifyChainGanache.rpc[0], - sourcifyChainGanache.chainId - ); - localProvider = new JsonRpcProvider( - `http://localhost:${GANACHE_PORT}`, - ethersNetwork, - { staticNetwork: ethersNetwork } - ); - signer = await localProvider.getSigner(); + signer = await sourcifyChainGanache.providers[0].getSigner(); }); describe('Verification tests', () => { @@ -438,6 +429,50 @@ describe('lib-sourcify tests', () => { }); describe('Unit tests', function () { + describe('SourcifyChain', () => { + it("Should fail to instantiate with empty rpc's", function () { + const emptyRpc = { ...ganacheChain, rpc: [] }; + try { + new SourcifyChain(emptyRpc); + throw new Error('Should have failed'); + } catch (err) { + if (err instanceof Error) { + expect(err.message).to.equal( + 'No RPC provider was given for this chain with id ' + + emptyRpc.chainId + + ' and name ' + + emptyRpc.name + ); + } else { + throw err; + } + } + }); + it('Should getBlock', async function () { + const block = await sourcifyChainGanache.getBlock(0); + expect(block?.number).equals(0); + }); + it('Should getBlockNumber', async function () { + const blockNumber = await sourcifyChainGanache.getBlockNumber(); + expect(blockNumber > 0); + }); + it('Should fail to get non-existing transaction', async function () { + try { + await sourcifyChainGanache.getTx( + '0x79ab5d59fcb70ca3f290aa39ed3f156a5c4b3897176aebd455cd20b6a30b107a' + ); + throw new Error('Should have failed'); + } catch (err) { + if (err instanceof Error) { + expect(err.message).to.equal( + 'None of the RPCs responded fetching tx 0x79ab5d59fcb70ca3f290aa39ed3f156a5c4b3897176aebd455cd20b6a30b107a on chain 0' + ); + } else { + throw err; + } + } + }); + }); it('Should calculateCreate2Address', async function () { expect( calculateCreate2Address( diff --git a/scripts/find_replace.sh b/scripts/find_replace.sh index 5d69c8cf8..d3e424658 100755 --- a/scripts/find_replace.sh +++ b/scripts/find_replace.sh @@ -73,7 +73,7 @@ if [ "$CIRCLE_BRANCH" == "master" ]; then CRONOSCAN_API_KEY=$CRONOSCAN_API_KEY_MASTER fi -for VAR_NAME in INFURA_ID ALCHEMY_ID AWS_S3_ACCESS_KEY_ID AWS_S3_SECRET_ACCESS_KEY IPFS_SECRET NPM_TOKEN PUBLIC_IP LOCAL_IP SESSION_SECRET ALCHEMY_ID_OPTIMISM ALCHEMY_ID_ARBITRUM CHAINSAFE_S3_ACCESS_KEY_ID CHAINSAFE_S3_SECRET_ACCESS_KEY ESTUARY_PINNING_SECRET WEB3_STORAGE_PINNING_SECRET CREATE2_CLIENT_TOKENS GRAFANA_HTTP_USER GRAFANA_HTTP_PASS ETHERSCAN_API_KEY ARBISCAN_API_KEY POLYGONSCAN_API_KEY BSCSCAN_API_KEY SNOWTRACE_API_KEY CELOSCAN_API_KEY MOONSCAN_MOONBEAM_API_KEY MOONSCAN_MOONRIVER_API_KEY BOBASCAN_API_KEY GNOSISSCAN_API_KEY OPTIMISMSCAN_API_KEY CRONOSCAN_API_KEY +for VAR_NAME in INFURA_ID ALCHEMY_ID CF_ACCESS_CLIENT_ID CF_ACCESS_CLIENT_SECRET AWS_S3_ACCESS_KEY_ID AWS_S3_SECRET_ACCESS_KEY IPFS_SECRET NPM_TOKEN PUBLIC_IP LOCAL_IP SESSION_SECRET ALCHEMY_ID_OPTIMISM ALCHEMY_ID_ARBITRUM CHAINSAFE_S3_ACCESS_KEY_ID CHAINSAFE_S3_SECRET_ACCESS_KEY ESTUARY_PINNING_SECRET WEB3_STORAGE_PINNING_SECRET CREATE2_CLIENT_TOKENS GRAFANA_HTTP_USER GRAFANA_HTTP_PASS ETHERSCAN_API_KEY ARBISCAN_API_KEY POLYGONSCAN_API_KEY BSCSCAN_API_KEY SNOWTRACE_API_KEY CELOSCAN_API_KEY MOONSCAN_MOONBEAM_API_KEY MOONSCAN_MOONRIVER_API_KEY BOBASCAN_API_KEY GNOSISSCAN_API_KEY OPTIMISMSCAN_API_KEY CRONOSCAN_API_KEY do echo "find_repace.sh: replacing $VAR_NAME" VAR_VAL=$(eval "echo \${$VAR_NAME}") diff --git a/scripts/monitor_ci.js b/scripts/monitor_ci.js index 5a8f98567..c95a23841 100755 --- a/scripts/monitor_ci.js +++ b/scripts/monitor_ci.js @@ -20,7 +20,7 @@ if (!chainID || !chainName) { process.exit(1); } -const artifact = require("../metacoin-source-verify/build/contracts/MetaCoin.json"); +const artifact = require("../metacoin-source-verify/MetaCoin.json"); const address = artifact.networks[chainID].address; async function main() { diff --git a/scripts/verification-e2e.js b/scripts/verification-e2e.js index 6e8ed4151..4b172970a 100644 --- a/scripts/verification-e2e.js +++ b/scripts/verification-e2e.js @@ -4,8 +4,10 @@ const { spawnSync } = require("child_process"); const deploymentChain = process.argv[2]; assert(deploymentChain, "No chain provided"); -const artifact = require("../metacoin-source-verify/build/contracts/MetaCoinSalted.json"); +const artifact = require("../metacoin-source-verify/MetaCoinSalted.json"); const deploymentAddress = artifact.networks[deploymentChain].address; +const buildInfoFilename = artifact.networks[deploymentChain].buildInfoFilename; + assert( deploymentAddress, `No address found - has the contract been deployed to chain ${deploymentChain}?` @@ -19,11 +21,9 @@ const args = [ "POST", serverUrl, "-F", - "files=@metacoin-source-verify/build/contracts/MetaCoinSalted.json", - "-F", - "files=@metacoin-source-verify/contracts/MetaCoinSalted.sol", + `files=@metacoin-source-verify/artifacts/build-info/${buildInfoFilename}`, "-F", - "files=@metacoin-source-verify/contracts/ConvertLib.sol", + "chosenContract=1", ]; if (deploymentChain) { diff --git a/src/common/SourcifyEventManager/SourcifyEventManager.ts b/src/common/SourcifyEventManager/SourcifyEventManager.ts index 386aaa158..ff91ca16c 100644 --- a/src/common/SourcifyEventManager/SourcifyEventManager.ts +++ b/src/common/SourcifyEventManager/SourcifyEventManager.ts @@ -4,10 +4,10 @@ import { EventManager, GenericEvents } from "../EventManager"; interface Events extends GenericEvents { "*": (event: string, argument: any) => void; "Verification.MatchStored": (match: Match) => void; + "Server.SourcifyChains.Warn": (obj: { message: string }) => void; "Monitor.Error.CantStart": (e: { chainId: string; message: string }) => void; "Monitor.Started": (obj: { chainId: string; - providerURL: string; lastBlockNumber: number; startBlock: number; }) => void; @@ -78,6 +78,7 @@ interface Events extends GenericEvents { export const SourcifyEventManager = new EventManager({ "*": [], "Verification.MatchStored": [], + "Server.SourcifyChains.Warn": [], "Monitor.Error.CantStart": [], "Monitor.Started": [], "Monitor.Stopped": [], diff --git a/src/monitor/monitor.ts b/src/monitor/monitor.ts index 3c93a01d6..c563862f0 100755 --- a/src/monitor/monitor.ts +++ b/src/monitor/monitor.ts @@ -1,11 +1,5 @@ import { SourceAddress } from "./util"; -import { - JsonRpcProvider, - Provider, - TransactionResponse, - WebSocketProvider, - getCreateAddress, -} from "ethers"; +import { TransactionResponse, getCreateAddress } from "ethers"; import SourceFetcher from "./source-fetcher"; import assert from "assert"; import { EventEmitter } from "stream"; @@ -29,7 +23,6 @@ const BLOCK_PAUSE_UPPER_LIMIT = parseInt(process.env.BLOCK_PAUSE_UPPER_LIMIT || "") || 30 * 1000; // default: 30 seconds const BLOCK_PAUSE_LOWER_LIMIT = parseInt(process.env.BLOCK_PAUSE_LOWER_LIMIT || "") || 0.5 * 1000; // default: 0.5 seconds -const PROVIDER_TIMEOUT = parseInt(process.env.PROVIDER_TIMEOUT || "") || 3000; function createsContract(tx: TransactionResponse): boolean { return !tx.to; @@ -40,7 +33,6 @@ function createsContract(tx: TransactionResponse): boolean { */ class ChainMonitor extends EventEmitter { private sourcifyChain: SourcifyChain; - private provider: Provider | undefined; private sourceFetcher: SourceFetcher; private verificationService: IVerificationService; private repositoryService: IRepositoryService; @@ -76,48 +68,18 @@ class ChainMonitor extends EventEmitter { const rawStartBlock = process.env[`MONITOR_START_${this.sourcifyChain.chainId}`]; - // iterate over RPCs to find a working one; log the search result - let found = false; - for (const providerURL of this.sourcifyChain.rpc) { - // const opts = { timeout: PROVIDER_TIMEOUT }; - // TODO: ethers provider does not support timeout https://github.com/ethers-io/ethers.js/issues/4122 - const provider = providerURL.startsWith("http") - ? // prettier vs. eslint indentation conflict here - /* eslint-disable indent*/ - new JsonRpcProvider(providerURL, { - name: this.sourcifyChain.name, - chainId: this.sourcifyChain.chainId, - }) - : new WebSocketProvider(providerURL, { - name: this.sourcifyChain.name, - chainId: this.sourcifyChain.chainId, - }); - /* eslint-enable indent*/ - try { - const lastBlockNumber = await provider.getBlockNumber(); - found = true; - - this.provider = provider; - - const startBlock = - rawStartBlock !== undefined - ? parseInt(rawStartBlock) - : lastBlockNumber; - - SourcifyEventManager.trigger("Monitor.Started", { - chainId: this.sourcifyChain.chainId.toString(), - providerURL, - lastBlockNumber, - startBlock, - }); - this.processBlock(startBlock); - break; - } catch (err) { - logger.info(`${providerURL} did not work: \n ${err}`); - } - } + try { + const lastBlockNumber = await this.sourcifyChain.getBlockNumber(); + const startBlock = + rawStartBlock !== undefined ? parseInt(rawStartBlock) : lastBlockNumber; - if (!found) { + SourcifyEventManager.trigger("Monitor.Started", { + chainId: this.sourcifyChain.chainId.toString(), + lastBlockNumber, + startBlock, + }); + this.processBlock(startBlock); + } catch (err) { SourcifyEventManager.trigger("Monitor.Error.CantStart", { chainId: this.sourcifyChain.chainId.toString(), message: "Couldn't find a working RPC node.", @@ -137,12 +99,7 @@ class ChainMonitor extends EventEmitter { }; private processBlock = (blockNumber: number) => { - if (!this.provider) - throw new Error( - `Can't process block ${blockNumber}. Provider not initialized` - ); - - this.provider + this.sourcifyChain .getBlock(blockNumber, true) .then((block) => { if (!block) { @@ -224,11 +181,9 @@ class ChainMonitor extends EventEmitter { if (retriesLeft-- <= 0) { return; } - if (!this.provider) - throw new Error(`Can't process bytecode. Provider not initialized`); - this.provider - .getCode(address) + this.sourcifyChain + .getBytecode(address) .then((bytecode) => { if (bytecode === "0x") { this.mySetTimeout( diff --git a/src/server/controllers/verification/session-state/session-state.handlers.ts b/src/server/controllers/verification/session-state/session-state.handlers.ts index fde4875b4..da503112c 100644 --- a/src/server/controllers/verification/session-state/session-state.handlers.ts +++ b/src/server/controllers/verification/session-state/session-state.handlers.ts @@ -14,7 +14,6 @@ import { import { PathBuffer, PathContent, - getBytecode, getIpfsGateway, isEmpty, performFetch, @@ -78,7 +77,7 @@ export async function addInputContractEndpoint(req: Request, res: Response) { const sourcifyChain = services.verification.supportedChainsMap[chainId]; - const bytecode = await getBytecode(sourcifyChain, address); + const bytecode = await sourcifyChain.getBytecode(address); const { ipfs: metadataIpfsCid } = bytecodeDecode(bytecode); diff --git a/src/server/server.ts b/src/server/server.ts index 20a0497ff..3d1ff8617 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -45,16 +45,28 @@ setLibSourcifyLogger({ if (level <= this.logLevel) { switch (level) { case 1: - logger.error(msg); + logger.error({ + labels: { event: "LibSourcify", level: "error" }, + message: msg, + }); break; case 2: - logger.warn(msg); + logger.warn({ + labels: { event: "LibSourcify", level: "warn" }, + message: msg, + }); break; case 3: - logger.info(msg); + logger.info({ + labels: { event: "LibSourcify", level: "info" }, + message: msg, + }); break; case 4: - logger.debug(msg); + logger.debug({ + labels: { event: "LibSourcify", level: "debug" }, + message: msg, + }); break; } } @@ -197,21 +209,33 @@ export class Server { res.status(200).send("Alive and kicking!") ); this.app.get("/chains", (_req, res) => { - const sourcifyChains = sourcifyChainsArray.map(({ rpc, ...rest }) => { - // Don't show Alchemy & Infura IDs - rpc = rpc.map((url) => { - if (url.includes("alchemy")) - return url.replace(/\/[^/]*$/, "/{ALCHEMY_ID}"); - else if (url.includes("infura")) - return url.replace(/\/[^/]*$/, "/{INFURA_ID}"); - else return url; - }); - return { - ...rest, - rpc, - etherscanAPI: etherscanAPIs[rest.chainId]?.apiURL, - }; - }); + const sourcifyChains = sourcifyChainsArray.map( + ({ rpc, name, title, chainId, supported, monitored }) => { + // Don't publish providers + // Don't show Alchemy & Infura IDs + rpc = rpc.map((url) => { + if (typeof url === "string") { + if (url.includes("alchemy")) + return url.replace(/\/[^/]*$/, "/{ALCHEMY_ID}"); + else if (url.includes("infura")) + return url.replace(/\/[^/]*$/, "/{INFURA_ID}"); + else return url; + } else { + // FetchRequest + return url.url; + } + }); + return { + name, + title, + chainId, + rpc, + supported, + monitored, + etherscanAPI: etherscanAPIs[chainId]?.apiURL, // Needed in the UI + }; + } + ); res.status(200).json(sourcifyChains); }); diff --git a/src/sourcify-chains.ts b/src/sourcify-chains.ts index eca44852e..b56c06f5d 100644 --- a/src/sourcify-chains.ts +++ b/src/sourcify-chains.ts @@ -11,6 +11,7 @@ import { import { etherscanAPIs } from "./config"; import { ValidationError } from "./common/errors"; import { logger } from "./common/loggerLoki"; +import { FetchRequest } from "ethers"; const allChains = chainsRaw as Chain[]; @@ -34,7 +35,7 @@ const AVALANCHE_SUBNET_SUFFIX = type ChainName = "eth" | "polygon" | "arb" | "opt"; const LOCAL_CHAINS: SourcifyChain[] = [ - { + new SourcifyChain({ name: "Ganache Localhost", shortName: "Ganache", chainId: 1337, @@ -46,8 +47,8 @@ const LOCAL_CHAINS: SourcifyChain[] = [ rpc: [`http://localhost:8545`], supported: true, monitored: true, - }, - { + }), + new SourcifyChain({ name: "Hardhat Network Localhost", shortName: "Hardhat Network", chainId: 31337, @@ -59,7 +60,7 @@ const LOCAL_CHAINS: SourcifyChain[] = [ rpc: [`http://localhost:8545`], supported: true, monitored: true, - }, + }), ]; interface SourcifyChainsExtensionsObject { @@ -78,14 +79,24 @@ function buildAlchemyAndCustomRpcURLs( chainName: ChainName, useOwn = false ) { - const rpcURLs: string[] = []; + const rpcURLs: SourcifyChain["rpc"] = []; if (useOwn) { const url = process.env[`NODE_URL_${chainSubName.toUpperCase()}`]; if (url) { - rpcURLs.push(url); + const ethersFetchReq = new FetchRequest(url); + ethersFetchReq.setHeader("Content-Type", "application/json"); + ethersFetchReq.setHeader( + "CF-Access-Client-Id", + process.env.CF_ACCESS_CLIENT_ID || "" + ); + ethersFetchReq.setHeader( + "CF-Access-Client-Secret", + process.env.CF_ACCESS_CLIENT_SECRET || "" + ); + rpcURLs.push(ethersFetchReq); } else { - SourcifyEventManager.trigger("Core.Error", { + SourcifyEventManager.trigger("Server.SourcifyChains.Warn", { message: `Environment variable NODE_URL_${chainSubName.toUpperCase()} not set!`, }); } @@ -106,17 +117,16 @@ function buildAlchemyAndCustomRpcURLs( break; } - if (!alchemyId) - logger.warn( - `Environment variable ALCHEMY_ID not set for ${chainName} ${chainSubName}!` - ); - - const domain = "g.alchemy.com"; - // No sepolia support yet - if (alchemyId && chainSubName !== "sepolia") + if (!alchemyId) { + SourcifyEventManager.trigger("Server.SourcifyChains.Warn", { + message: `Environment variable ALCHEMY_ID not set for ${chainName} ${chainSubName}!`, + }); + } else { + const domain = "g.alchemy.com"; rpcURLs.push( `https://${chainName}-${chainSubName}.${domain}/v2/${alchemyId}` ); + } return rpcURLs; } @@ -161,9 +171,7 @@ const sourcifyChainsExtensions: SourcifyChainsExtensionsObject = { // Ethereum Sepolia Testnet supported: true, monitored: true, - rpc: buildAlchemyAndCustomRpcURLs("sepolia", "eth", true).concat( - "https://rpc.sepolia.org" - ), + rpc: buildAlchemyAndCustomRpcURLs("sepolia", "eth", true), contractFetchAddress: generateEtherscanCreatorTxAPI("11155111"), }, "3": { @@ -953,7 +961,11 @@ for (const i in allChains) { if (chainId in sourcifyChainsExtensions) { const sourcifyExtension = sourcifyChainsExtensions[chainId]; - const sourcifyChain = { ...chain, ...sourcifyExtension } as SourcifyChain; + // sourcifyExtension is spread later to overwrite chain values, rpc specifically + const sourcifyChain = new SourcifyChain({ + ...chain, + ...sourcifyExtension, + }); sourcifyChainsMap[chainId] = sourcifyChain; } }