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

feat: snapshot status indicator #1123

Merged
merged 19 commits into from
Sep 18, 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
2 changes: 2 additions & 0 deletions gatsby-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import Segment from "decentraland-gatsby/dist/components/Development/Segment"
import { SEGMENT_KEY, SSO_URL } from "./src/constants"
import { flattenMessages } from "./src/utils/intl"
import en from "./src/intl/en.json"
import SnapshotStatus from "./src/components/Debug/SnapshotStatus"

const queryClient = new QueryClient()

Expand All @@ -49,6 +50,7 @@ export const wrapPageElement = ({ element, props }) => {
<IntlProvider defaultLocale='en' locale='en' messages={flattenMessages(en)}>
<IdentityModalContextProvider>
<BurgerMenuStatusContextProvider>
<SnapshotStatus />
<Layout {...props} rightMenu={<Navbar />}>
{element}
</Layout>
Expand Down
20 changes: 20 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"keccak": "^3.0.1",
"lodash": "^4.17.21",
"nft.storage": "^7.1.1",
"node-cache": "^5.1.2",
"node-pg-migrate": "^6.2.1",
"numeral": "^2.0.6",
"patch-package": "^6.4.7",
Expand Down
5 changes: 5 additions & 0 deletions src/back/jobs/PingSnapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SnapshotService } from '../../services/SnapshotService'

export async function pingSnapshot() {
await SnapshotService.ping()
}
2 changes: 1 addition & 1 deletion src/back/routes/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { WithAuth, auth } from 'decentraland-gatsby/dist/entities/Auth/middlewar
import handleAPI from 'decentraland-gatsby/dist/entities/Route/handle'
import routes from 'decentraland-gatsby/dist/entities/Route/routes'

import { DEBUG_ADDRESSES } from '../../entities/Debug/isDebugAddress'
import { DEBUG_ADDRESSES } from '../../constants'
import { ErrorService } from '../../services/ErrorService'
import { giveAndRevokeLandOwnerBadges, giveTopVoterBadges, runQueuedAirdropJobs } from '../jobs/BadgeAirdrop'
import { validateDebugAddress } from '../utils/validations'
Expand Down
13 changes: 9 additions & 4 deletions src/back/routes/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import handleAPI from 'decentraland-gatsby/dist/entities/Route/handle'
import routes from 'decentraland-gatsby/dist/entities/Route/routes'
import { Request } from 'express'

import { SnapshotVote } from '../../clients/SnapshotGraphqlTypes'
import { SnapshotVote } from '../../clients/SnapshotTypes'
import { SnapshotService } from '../../services/SnapshotService'
import { validateAddress, validateDates, validateFields, validateProposalSnapshotId } from '../utils/validations'

export default routes((router) => {
router.get('/snapshot/status-space/:spaceName', handleAPI(getStatusAndSpace))
router.get('/snapshot/status', handleAPI(getStatus))
router.get('/snapshot/config/:spaceName', handleAPI(getConfig))
router.post('/snapshot/votes', handleAPI(getAddressesVotes))
router.get('/snapshot/votes/:proposalSnapshotId', handleAPI(getProposalVotes))
router.post('/snapshot/votes/all', handleAPI(getAllVotesBetweenDates))
Expand All @@ -19,9 +20,13 @@ export default routes((router) => {
router.get('/snapshot/proposal-scores/:proposalSnapshotId', handleAPI(getProposalScores))
})

async function getStatusAndSpace(req: Request<{ spaceName?: string }>) {
async function getStatus(req: Request) {
return await SnapshotService.getStatus()
}

async function getConfig(req: Request<{ spaceName?: string }>) {
const { spaceName } = req.params
return await SnapshotService.getStatusAndSpace(spaceName)
return await SnapshotService.getConfig(spaceName)
}

async function getAddressesVotes(req: Request) {
Expand Down
2 changes: 1 addition & 1 deletion src/back/routes/votes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Request } from 'express'
import isNumber from 'lodash/isNumber'

import { SnapshotGraphql } from '../../clients/SnapshotGraphql'
import { SnapshotVote } from '../../clients/SnapshotGraphqlTypes'
import { SnapshotVote } from '../../clients/SnapshotTypes'
import ProposalModel from '../../entities/Proposal/model'
import { ProposalAttributes } from '../../entities/Proposal/types'
import VotesModel from '../../entities/Votes/model'
Expand Down
2 changes: 1 addition & 1 deletion src/back/services/vote.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SnapshotVote } from '../../clients/SnapshotGraphqlTypes'
import { SnapshotVote } from '../../clients/SnapshotTypes'
import { VOTES_VP_THRESHOLD } from '../../constants'
import VoteModel from '../../entities/Votes/model'
import { VoteCount, Voter } from '../../entities/Votes/types'
Expand Down
2 changes: 1 addition & 1 deletion src/back/utils/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import RequestError from 'decentraland-gatsby/dist/entities/Route/error'
import isEthereumAddress from 'validator/lib/isEthereumAddress'
import isUUID from 'validator/lib/isUUID'

import { SnapshotProposal } from '../../clients/SnapshotGraphqlTypes'
import { SnapshotProposal } from '../../clients/SnapshotTypes'
import isDebugAddress from '../../entities/Debug/isDebugAddress'

export function validateDates(start?: string, end?: string) {
Expand Down
14 changes: 10 additions & 4 deletions src/clients/Governance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ import Time from '../utils/date/Time'
import { TransparencyBudget } from './DclData'
import {
DetailedScores,
SnapshotConfig,
SnapshotProposal,
SnapshotSpace,
SnapshotStatus,
SnapshotVote,
VpDistribution,
} from './SnapshotGraphqlTypes'
} from './SnapshotTypes'
import { VestingInfo } from './VestingData'

type NewProposalMap = {
Expand Down Expand Up @@ -527,14 +528,19 @@ export class Governance extends API {
return response.data
}

async getSnapshotStatusAndSpace(spaceName?: string) {
const response = await this.fetch<ApiResponse<{ status: SnapshotStatus; space: SnapshotSpace }>>(
`/snapshot/status-space/${spaceName}`,
async getSnapshotConfigAndSpace(spaceName?: string) {
const response = await this.fetch<ApiResponse<{ config: SnapshotConfig; space: SnapshotSpace }>>(
`/snapshot/config/${spaceName}`,
this.options().method('GET')
)
return response.data
}

async getSnapshotStatus() {
const response = await this.fetch<ApiResponse<SnapshotStatus>>(`/snapshot/status`, this.options().method('GET'))
return response.data
}

async getAddressesVotes(addresses: string[]) {
const result = await this.fetch<ApiResponse<VotedProposal[]>>(
`/snapshot/votes/`,
Expand Down
34 changes: 29 additions & 5 deletions src/clients/SnapshotApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CancelProposal, ProposalType, Vote } from '@snapshot-labs/snapshot.js/d
import logger from 'decentraland-gatsby/dist/entities/Development/logger'
import env from 'decentraland-gatsby/dist/utils/env'

import { DEBUG_ADDRESSES } from '../constants'
import {
SNAPSHOT_ADDRESS,
SNAPSHOT_API_KEY,
Expand Down Expand Up @@ -155,11 +156,9 @@ export class SnapshotApi {
}

async getScores(addresses: string[]) {
const formattedAddresses = addresses.map((address) => getChecksumAddress(address))
const spaceName = SnapshotApi.getSpaceName()
const network = getEnvironmentChainId().toString()
const strategies = (await SnapshotGraphql.get().getSpace(spaceName)).strategies
const scoreApiUrl = `https://score.snapshot.org/?apiKey=${SNAPSHOT_API_KEY}`
const { formattedAddresses, spaceName, network, strategies, scoreApiUrl } = await this.prepareScoresQueryArgs(
addresses
)

try {
const scores = await snapshot.utils.getScores(
Expand Down Expand Up @@ -188,4 +187,29 @@ export class SnapshotApi {
private static toSnapshotTimestamp(time: number) {
return Number(time.toString().slice(0, -3))
}

async ping(addressesSample: string[]) {
const addresses = addressesSample.length === 0 ? DEBUG_ADDRESSES : addressesSample
try {
const { formattedAddresses, spaceName, network, strategies, scoreApiUrl } = await this.prepareScoresQueryArgs(
addresses
)
const now = new Date()
const startTime = now.getTime()
await snapshot.utils.getScores(spaceName, strategies, network, formattedAddresses, undefined, scoreApiUrl)
const endTime = new Date().getTime()
return endTime - startTime
} catch (error) {
return -1 // Return -1 to indicate API failures
}
}

private async prepareScoresQueryArgs(addresses: string[]) {
const formattedAddresses = addresses.map((address) => getChecksumAddress(address))
const spaceName = SnapshotApi.getSpaceName()
const network = getEnvironmentChainId().toString()
const strategies = (await SnapshotGraphql.get().getSpace(spaceName)).strategies
const scoreApiUrl = `https://score.snapshot.org/?apiKey=${SNAPSHOT_API_KEY}`
return { formattedAddresses, spaceName, network, strategies, scoreApiUrl }
}
}
89 changes: 48 additions & 41 deletions src/clients/SnapshotGraphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,46 @@ import env from 'decentraland-gatsby/dist/utils/env'
import uniqBy from 'lodash/uniqBy'

import { SNAPSHOT_API, SNAPSHOT_API_KEY, SNAPSHOT_SPACE } from '../entities/Snapshot/constants'
import { getAMonthAgo } from '../utils/date/aMonthAgo'
import { ErrorCategory } from '../utils/errorCategories'

import { ErrorClient } from './ErrorClient'
import {
SnapshotConfig,
SnapshotProposal,
SnapshotProposalContent,
SnapshotProposalResponse,
SnapshotProposalsResponse,
SnapshotQueryResponse,
SnapshotScoresState,
SnapshotSpace,
SnapshotStatus,
SnapshotVote,
SnapshotVoteResponse,
SnapshotVpResponse,
StrategyOrder,
VpDistribution,
} from './SnapshotGraphqlTypes'
} from './SnapshotTypes'
import { inBatches, trimLastForwardSlash } from './utils'

export const getQueryTimestamp = (dateTimestamp: number) => Math.round(dateTimestamp / 1000)

const GRAPHQL_ENDPOINT = `/graphql`
const BATCH_SIZE = 1000
const OLDEST_PROPOSAL_TIMESTAMP = 1621036800
const GET_VOTES_QUERY = `
query getVotes($space: String!, $start: Int!, $end: Int!, $first: Int!) {
votes(where: {space: $space, created_gte: $start, created_lt: $end}, orderBy: "created", orderDirection: asc, first: $first) {
id
voter
created
vp
choice
proposal {
id
choices
}
}
}
`

export class SnapshotGraphql extends API {
static Url = SNAPSHOT_API || 'https://hub.snapshot.org/'
Expand All @@ -52,12 +67,12 @@ export class SnapshotGraphql extends API {
if (SNAPSHOT_API_KEY) this.defaultOptions.header('x-api-key', SNAPSHOT_API_KEY)
}

async getStatus() {
const status = await this.fetch<SnapshotStatus>('/api/')
async getConfig() {
const snapshotConfig = await this.fetch<SnapshotConfig>('/api/')

return {
...status,
version: status.version.split('#')[0],
...snapshotConfig,
version: snapshotConfig.version.split('#')[0],
}
}

Expand Down Expand Up @@ -235,22 +250,6 @@ export class SnapshotGraphql extends API {
}

async getAllVotesBetweenDates(start: Date, end: Date): Promise<SnapshotVote[]> {
const query = `
query getVotes($space: String!, $start: Int!, $end: Int!, $first: Int!) {
votes(where: {space: $space, created_gte: $start, created_lt: $end}, orderBy: "created", orderDirection: asc, first: $first) {
id
voter
created
vp
choice
proposal {
id
choices
}
}
}
`

let allResults: SnapshotVote[] = []
let hasNext = true
let created = getQueryTimestamp(start.getTime())
Expand All @@ -268,7 +267,7 @@ export class SnapshotGraphql extends API {
const result = await this.fetch<SnapshotVoteResponse>(
GRAPHQL_ENDPOINT,
this.options().method('POST').json({
query,
GET_VOTES_QUERY,
variables,
})
)
Expand Down Expand Up @@ -444,23 +443,31 @@ export class SnapshotGraphql extends API {
return result?.data?.proposal
}

async hasVoted(address: string) {
const query = `query HasVoted($space: String!, $address: String!, $created: Int!) {
votes(
where: { space: $space, voter: $address, created_gt: $created}
first: 1
) {
voter
}
async ping() {
const now = new Date()
const startTime = now.getTime()
try {
const startDate = getAMonthAgo(now).getTime()
const endDate = now.getTime()
const response = await this.fetch<SnapshotVoteResponse>(
GRAPHQL_ENDPOINT,
this.options()
.method('POST')
.json({
query: GET_VOTES_QUERY,
variables: {
space: SNAPSHOT_SPACE,
first: 10,
start: getQueryTimestamp(startDate),
end: getQueryTimestamp(endDate),
},
})
)
const endTime = new Date().getTime()
const addressesSample = response?.data?.votes.map((vote: SnapshotVote) => vote.voter)
return { responseTime: endTime - startTime, addressesSample }
} catch (error) {
return { responseTime: -1, addressesSample: [] } // Return -1 to indicate API failures
}
`
const result = await this.fetch<SnapshotQueryResponse<{ votes: Pick<SnapshotVote, 'voter'>[] }>>(
GRAPHQL_ENDPOINT,
this.options()
.method('POST')
.json({ query, variables: { address, space: SNAPSHOT_SPACE, created: OLDEST_PROPOSAL_TIMESTAMP } })
)

return result?.data?.votes.length > 0
}
}
2 changes: 1 addition & 1 deletion src/clients/SnapshotSubgraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fetch from 'isomorphic-fetch'
import { SNAPSHOT_QUERY_ENDPOINT } from '../entities/Snapshot/constants'
import { PICKED_BY_QUERY } from '../entities/Snapshot/queries'

import { Delegation } from './SnapshotGraphqlTypes'
import { Delegation } from './SnapshotTypes'
import { inBatches, trimLastForwardSlash } from './utils'

export type DelegationQueryResult = {
Expand Down
Loading