From c79c84c618539e8ceb8c2edf56190aa8950b4fce Mon Sep 17 00:00:00 2001 From: Mark Grothe Date: Thu, 29 Sep 2022 12:47:24 -0500 Subject: [PATCH 1/5] fix: swap ipfs gateway --- src/ui-config/governanceConfig.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui-config/governanceConfig.ts b/src/ui-config/governanceConfig.ts index f97c1a3b66..c7b7363299 100644 --- a/src/ui-config/governanceConfig.ts +++ b/src/ui-config/governanceConfig.ts @@ -42,6 +42,6 @@ export const governanceConfig: GovernanceConfig = { AAVE_GOVERNANCE_V2_EXECUTOR_LONG: '0xEE56e2B3D491590B5b31738cC34d5232F378a8D5', AAVE_GOVERNANCE_V2_HELPER: '0x16ff7583ea21055bf5f929ec4b896d997ff35847', }, - ipfsGateway: 'https://gateway.pinata.cloud/ipfs', - fallbackIpfsGateway: 'https://cloudflare-ipfs.com/ipfs', + ipfsGateway: 'https://cloudflare-ipfs.com/ipfs', + fallbackIpfsGateway: 'https://gateway.pinata.cloud/ipfs', }; From a1b7019c19e021c15b344c23352343f94464db40 Mon Sep 17 00:00:00 2001 From: Mark Grothe Date: Thu, 29 Sep 2022 15:12:20 -0500 Subject: [PATCH 2/5] fix: replace pinata gateway with ipfs one --- src/modules/governance/utils/getProposalMetadata.ts | 6 +----- src/ui-config/governanceConfig.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/modules/governance/utils/getProposalMetadata.ts b/src/modules/governance/utils/getProposalMetadata.ts index 84e47280c7..524d043bcf 100644 --- a/src/modules/governance/utils/getProposalMetadata.ts +++ b/src/modules/governance/utils/getProposalMetadata.ts @@ -18,11 +18,7 @@ export async function getProposalMetadata( ? base58.encode(Buffer.from(`1220${hash.slice(2)}`, 'hex')) : hash; if (MEMORIZE[ipfsHash]) return MEMORIZE[ipfsHash]; - const ipfsResponse: Response = await fetch(getLink(ipfsHash, gateway), { - headers: { - 'Content-Type': 'application/json', - }, - }); + const ipfsResponse: Response = await fetch(getLink(ipfsHash, gateway)); if (!ipfsResponse.ok) { throw Error('Fetch not working'); } diff --git a/src/ui-config/governanceConfig.ts b/src/ui-config/governanceConfig.ts index c7b7363299..28deb75d63 100644 --- a/src/ui-config/governanceConfig.ts +++ b/src/ui-config/governanceConfig.ts @@ -43,5 +43,5 @@ export const governanceConfig: GovernanceConfig = { AAVE_GOVERNANCE_V2_HELPER: '0x16ff7583ea21055bf5f929ec4b896d997ff35847', }, ipfsGateway: 'https://cloudflare-ipfs.com/ipfs', - fallbackIpfsGateway: 'https://gateway.pinata.cloud/ipfs', + fallbackIpfsGateway: 'https://ipfs.io/ipfs', }; From 2d29cf77ae75df7dcf2949e2454bb306ce9d57d7 Mon Sep 17 00:00:00 2001 From: Mark Grothe Date: Thu, 29 Sep 2022 15:51:07 -0500 Subject: [PATCH 3/5] fix: use retry logic anywhere we try to get proposal from ipfs --- pages/governance/ipfs-preview.governance.tsx | 3 +- .../governance/proposal/index.governance.tsx | 30 ++++--------------- src/modules/governance/ProposalsList.tsx | 6 +++- .../governance/utils/getProposalMetadata.ts | 20 +++++++++++++ 4 files changed, 32 insertions(+), 27 deletions(-) diff --git a/pages/governance/ipfs-preview.governance.tsx b/pages/governance/ipfs-preview.governance.tsx index ed99c14cbc..ec41670e42 100644 --- a/pages/governance/ipfs-preview.governance.tsx +++ b/pages/governance/ipfs-preview.governance.tsx @@ -15,10 +15,11 @@ export default function IpfsPreview() { const [ipfs, setIpfs] = useState(); async function fetchIpfs() { + const proposalMetadata = await getProposalMetadata(ipfsHash, governanceConfig.ipfsGateway); const newIpfs = { id: -1, originalHash: ipfsHash, - ...(await getProposalMetadata(ipfsHash, governanceConfig.ipfsGateway)), + ...proposalMetadata, }; setIpfs(newIpfs); } diff --git a/pages/governance/proposal/index.governance.tsx b/pages/governance/proposal/index.governance.tsx index 858095d56e..89a7234f90 100644 --- a/pages/governance/proposal/index.governance.tsx +++ b/pages/governance/proposal/index.governance.tsx @@ -19,12 +19,12 @@ export default function DynamicProposal() { const [ipfs, setIpfs] = useState(); const [fetchMetadataError, setFetchMetadataError] = useState(false); - // TODO: Abstract out this recursive try/fallback approach so it may be used in other places where we fetch the proposal metadata and may have errors from the initial gateway async function initialize(_ipfsGateway: string, _useFallback: boolean) { + const { values, ...rest } = await governanceContract.getProposal({ proposalId: id }); + const proposal = await enhanceProposalWithTimes(rest); + setProposal(proposal); + try { - const { values, ...rest } = await governanceContract.getProposal({ proposalId: id }); - const proposal = await enhanceProposalWithTimes(rest); - setProposal(proposal); const ipfsMetadata = await getProposalMetadata(proposal.ipfsHash, _ipfsGateway); const newIpfs = { id, @@ -33,27 +33,7 @@ export default function DynamicProposal() { }; setIpfs(newIpfs); } catch (e) { - // Recursion - Try again once with our fallback API - // Base case: If we are retrying with our fallback and it fails, return - // Rescursive case: If we haven't retried with our fallback yet, try it once - if (_useFallback) { - console.groupCollapsed('Fetching proposal metadata from IPFS...'); - console.info('failed with', _ipfsGateway); - console.info('exiting'); - console.error(e); - console.groupEnd(); - // To prevent continually adding onto the callstack with failed requests, return and show an error message in the UI - setFetchMetadataError(true); - return; - } else { - const fallback = governanceConfig.fallbackIpfsGateway; - console.groupCollapsed('Fetching proposal metadata from IPFS...'); - console.info('failed with', _ipfsGateway); - console.info('retrying with', fallback); - console.error(e); - console.groupEnd(); - initialize(fallback, true); - } + setFetchMetadataError(true); } } diff --git a/src/modules/governance/ProposalsList.tsx b/src/modules/governance/ProposalsList.tsx index a0f02c9a77..126090352f 100644 --- a/src/modules/governance/ProposalsList.tsx +++ b/src/modules/governance/ProposalsList.tsx @@ -39,11 +39,15 @@ export function ProposalsList({ proposals: initialProposals }: GovernancePagePro for (let i = proposals.length; i < count; i++) { const { values, ...rest } = await governanceContract.getProposal({ proposalId: i }); const proposal = await enhanceProposalWithTimes(rest); + const proposalMetadata = await getProposalMetadata( + proposal.ipfsHash, + governanceConfig.ipfsGateway + ); nextProposals.push({ ipfs: { id: i, originalHash: proposal.ipfsHash, - ...(await getProposalMetadata(proposal.ipfsHash, governanceConfig.ipfsGateway)), + ...proposalMetadata, }, proposal: proposal, prerendered: false, diff --git a/src/modules/governance/utils/getProposalMetadata.ts b/src/modules/governance/utils/getProposalMetadata.ts index 524d043bcf..5100bb0371 100644 --- a/src/modules/governance/utils/getProposalMetadata.ts +++ b/src/modules/governance/utils/getProposalMetadata.ts @@ -2,6 +2,7 @@ import { ProposalMetadata } from '@aave/contract-helpers'; import { base58 } from 'ethers/lib/utils'; import matter from 'gray-matter'; import fetch from 'isomorphic-unfetch'; +import { governanceConfig } from 'src/ui-config/governanceConfig'; export function getLink(hash: string, gateway: string): string { return `${gateway}/${hash}`; @@ -14,6 +15,25 @@ export async function getProposalMetadata( hash: string, gateway: string ): Promise { + try { + return await fetchFromIpfs(hash, gateway); + } catch (e) { + console.groupCollapsed('Fetching proposal metadata from IPFS...'); + console.info('failed with', gateway); + if (gateway === governanceConfig.ipfsGateway) { + console.info('retrying with', governanceConfig.fallbackIpfsGateway); + console.error(e); + console.groupEnd(); + return getProposalMetadata(hash, governanceConfig.fallbackIpfsGateway); + } + console.info('exiting'); + console.error(e); + console.groupEnd(); + throw e; + } +} + +async function fetchFromIpfs(hash: string, gateway: string): Promise { const ipfsHash = hash.startsWith('0x') ? base58.encode(Buffer.from(`1220${hash.slice(2)}`, 'hex')) : hash; From 1a6d33634a2f65455ad24218ee0db76a81af0651 Mon Sep 17 00:00:00 2001 From: Mark Grothe Date: Thu, 29 Sep 2022 16:10:03 -0500 Subject: [PATCH 4/5] fix: remove unused param --- pages/governance/proposal/index.governance.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/governance/proposal/index.governance.tsx b/pages/governance/proposal/index.governance.tsx index 89a7234f90..170b5aa14c 100644 --- a/pages/governance/proposal/index.governance.tsx +++ b/pages/governance/proposal/index.governance.tsx @@ -19,7 +19,7 @@ export default function DynamicProposal() { const [ipfs, setIpfs] = useState(); const [fetchMetadataError, setFetchMetadataError] = useState(false); - async function initialize(_ipfsGateway: string, _useFallback: boolean) { + async function initialize(_ipfsGateway: string) { const { values, ...rest } = await governanceContract.getProposal({ proposalId: id }); const proposal = await enhanceProposalWithTimes(rest); setProposal(proposal); @@ -38,7 +38,7 @@ export default function DynamicProposal() { } useEffect(() => { - id && initialize(governanceConfig.ipfsGateway, false); + id && initialize(governanceConfig.ipfsGateway); }, [id]); return ; From 25c214c8476e96a570c4f5b1f65f6b291ecf64e5 Mon Sep 17 00:00:00 2001 From: Drew Cook Date: Mon, 3 Oct 2022 11:24:56 +0200 Subject: [PATCH 5/5] chore: add JSDoc comments --- .../governance/utils/getProposalMetadata.ts | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/modules/governance/utils/getProposalMetadata.ts b/src/modules/governance/utils/getProposalMetadata.ts index 5100bb0371..92a5157197 100644 --- a/src/modules/governance/utils/getProposalMetadata.ts +++ b/src/modules/governance/utils/getProposalMetadata.ts @@ -4,13 +4,27 @@ import matter from 'gray-matter'; import fetch from 'isomorphic-unfetch'; import { governanceConfig } from 'src/ui-config/governanceConfig'; +type MemorizeMetadata = Record; +const MEMORIZE: MemorizeMetadata = {}; + +/** + * Composes a URI based off of a given IPFS CID hash and gateway + * @param {string} hash - The IPFS CID hash + * @param {string} gateway - The IPFS gateway host + * @returns string + */ export function getLink(hash: string, gateway: string): string { return `${gateway}/${hash}`; } -type MemorizeMetadata = Record; - -const MEMORIZE: MemorizeMetadata = {}; +/** + * Fetches the IPFS metadata JSON from our preferred public gateway, once. + * If the gateway fails, attempt to fetch recursively with a fallback gateway, once. + * If the fallback fails, throw an error. + * @param {string} hash - The IPFS CID hash to query + * @param {string} gateway - The IPFS gateway host + * @returns Promise + */ export async function getProposalMetadata( hash: string, gateway: string @@ -20,12 +34,16 @@ export async function getProposalMetadata( } catch (e) { console.groupCollapsed('Fetching proposal metadata from IPFS...'); console.info('failed with', gateway); + + // Primary gateway failed, retry with fallback if (gateway === governanceConfig.ipfsGateway) { console.info('retrying with', governanceConfig.fallbackIpfsGateway); console.error(e); console.groupEnd(); return getProposalMetadata(hash, governanceConfig.fallbackIpfsGateway); } + + // Fallback gateway failed, exit console.info('exiting'); console.error(e); console.groupEnd(); @@ -33,16 +51,27 @@ export async function getProposalMetadata( } } +/** + * Fetches data from a provided IPFS gateway with a simple caching mechanism. + * Cache keys are the hashes, values are ProposalMetadata objects. + * The cache does not implement any invalidation mechanisms nor sets expiries. + * @param {string} hash - The IPFS CID hash to query + * @param {string} gateway - The IPFS gateway host + * @returns Promise + */ async function fetchFromIpfs(hash: string, gateway: string): Promise { + // Read from cache const ipfsHash = hash.startsWith('0x') ? base58.encode(Buffer.from(`1220${hash.slice(2)}`, 'hex')) : hash; if (MEMORIZE[ipfsHash]) return MEMORIZE[ipfsHash]; + + // Fetch const ipfsResponse: Response = await fetch(getLink(ipfsHash, gateway)); - if (!ipfsResponse.ok) { - throw Error('Fetch not working'); - } + if (!ipfsResponse.ok) throw Error('Fetch not working'); const clone = await ipfsResponse.clone(); + + // Store in cache try { const response: ProposalMetadata = await ipfsResponse.json(); const { content, data } = matter(response.description);