Skip to content

Meridian #226

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

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
6 changes: 4 additions & 2 deletions bin/spark.js → bin/meridian.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ await migrate(client)
const getCurrentRound = await createRoundGetter(client)

const round = getCurrentRound()
assert(!!round, 'cannot obtain the current Spark round number')
console.log('SPARK round number at service startup:', round.sparkRoundNumber)
assert(!!round, 'cannot obtain the current module round numbers')
for (const [moduleId, moduleRoundNumber] of round.moduleRoundNumbers.entries()) {
console.log('%s round number at service startup: %s', moduleId, moduleRoundNumber)
}

const logger = {
error: console.error,
Expand Down
141 changes: 34 additions & 107 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ import Sentry from '@sentry/node'
import getRawBody from 'raw-body'
import assert from 'http-assert'
import { validate } from './lib/validate.js'
import { mapRequestToInetGroup } from './lib/inet-grouping.js'
import { satisfies } from 'compare-versions'
import * as spark from './lib/spark.js'
import * as voyager from './lib/voyager.js'

const moduleImplementations = {
0: spark,
1: voyager
}

const handler = async (req, res, client, getCurrentRound, domain) => {
if (req.headers.host.split(':')[0] !== domain) {
Expand All @@ -22,8 +27,10 @@ const handler = async (req, res, client, getCurrentRound, domain) => {
} else if (segs[0] === 'measurements' && req.method === 'GET') {
await getMeasurement(req, res, client, Number(segs[1]))
} else if (segs[0] === 'rounds' && segs[1] === 'meridian' && req.method === 'GET') {
// TODO: Add moduleId
await getMeridianRoundDetails(req, res, client, segs[2], segs[3])
} else if (segs[0] === 'rounds' && req.method === 'GET') {
// TODO: Add moduleId
await getRoundDetails(req, res, client, getCurrentRound, segs[1])
} else if (segs[0] === 'inspect-request' && req.method === 'GET') {
await inspectRequest(req, res)
Expand All @@ -33,122 +40,57 @@ const handler = async (req, res, client, getCurrentRound, domain) => {
}

const createMeasurement = async (req, res, client, getCurrentRound) => {
const { sparkRoundNumber } = getCurrentRound()
const body = await getRawBody(req, { limit: '100kb' })
const measurement = JSON.parse(body)
validate(measurement, 'sparkVersion', { type: 'string', required: false })

validate(measurement, 'zinniaVersion', { type: 'string', required: false })
assert(
typeof measurement.sparkVersion === 'string' && satisfies(measurement.sparkVersion, '>=1.9.0'),
410, 'OUTDATED CLIENT'
)

// Backwards-compatibility with older clients sending walletAddress instead of participantAddress
// We can remove this after enough SPARK clients are running the new version (mid-October 2023)
if (!('participantAddress' in measurement) && ('walletAddress' in measurement)) {
validate(measurement, 'walletAddress', { type: 'string', required: true })
measurement.participantAddress = measurement.walletAddress
delete measurement.walletAddress
}

validate(measurement, 'cid', { type: 'string', required: true })
validate(measurement, 'providerAddress', { type: 'string', required: true })
validate(measurement, 'protocol', { type: 'string', required: true })
validate(measurement, 'participantAddress', { type: 'string', required: true })
validate(measurement, 'timeout', { type: 'boolean', required: false })
validate(measurement, 'startAt', { type: 'date', required: true })
validate(measurement, 'statusCode', { type: 'number', required: false })
validate(measurement, 'firstByteAt', { type: 'date', required: false })
validate(measurement, 'endAt', { type: 'date', required: false })
validate(measurement, 'byteLength', { type: 'number', required: false })
validate(measurement, 'attestation', { type: 'string', required: false })
validate(measurement, 'carTooLarge', { type: 'boolean', required: false })
validate(measurement, 'carChecksum', { type: 'string', required: false })
validate(measurement, 'indexerResult', { type: 'string', required: false })
validate(measurement.moduleId, { type: 'number', required: false })

const moduleId = measurement.moduleId || 0
const moduleImplementation = moduleImplementations[moduleId]
assert(moduleImplementation, `Unknown moduleId: ${moduleId}`)

const inetGroup = await mapRequestToInetGroup(client, req)
moduleImplementation.validateMeasurement(measurement)

const { rows } = await client.query(`
INSERT INTO measurements (
spark_version,
zinnia_version,
cid,
provider_address,
protocol,
participant_address,
timeout,
start_at,
status_code,
first_byte_at,
end_at,
byte_length,
attestation,
inet_group,
car_too_large,
car_checksum,
indexer_result,
completed_at_round
)
VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18
)
RETURNING id
`, [
measurement.sparkVersion,
measurement.zinniaVersion,
measurement.cid,
measurement.providerAddress,
measurement.protocol,
measurement.participantAddress,
measurement.timeout || false,
parseOptionalDate(measurement.startAt),
measurement.statusCode,
parseOptionalDate(measurement.firstByteAt),
parseOptionalDate(measurement.endAt),
measurement.byteLength,
measurement.attestation,
inetGroup,
measurement.carTooLarge ?? false,
measurement.carChecksum,
measurement.indexerResult,
sparkRoundNumber
INSERT INTO measurements (module_id, data, completed_at_round)
VALUES ($1, $2, $3)
RETURNING id
`, [
moduleId,
JSON.stringify(moduleImplementation.sanitizeMeasurement(measurement)),
getCurrentRound().moduleRoundNumbers.get(moduleId)
])

json(res, { id: rows[0].id })
}

const getMeasurement = async (req, res, client, measurementId) => {
assert(!Number.isNaN(measurementId), 400, 'Invalid RetrievalResult ID')
const { rows: [resultRow] } = await client.query(`
SELECT *
FROM measurements
WHERE id = $1
`, [
measurementId
])
const { rows: [resultRow] } = await client.query(
`SELECT module_id, data, completed_at_round FROM measurements WHERE id = $1`,
[measurementId]
)
assert(resultRow, 404, 'Measurement Not Found')
json(res, {
id: resultRow.id,
cid: resultRow.cid,
providerAddress: resultRow.provider_address,
protocol: resultRow.protocol,
sparkVersion: resultRow.spark_version,
zinniaVersion: resultRow.zinnia_version,
createdAt: resultRow.created_at,
finishedAt: resultRow.finished_at,
timeout: resultRow.timeout,
startAt: resultRow.start_at,
statusCode: resultRow.status_code,
firstByteAt: resultRow.first_byte_at,
endAt: resultRow.end_at,
byteLength: resultRow.byte_length,
carTooLarge: resultRow.car_too_large,
attestation: resultRow.attestation
...JSON.parse(resultRow.data),
id: measurementId,
moduleId: resultRow.module_id,
moduleRound: resultRow.completed_at_round
})
}

const getRoundDetails = async (req, res, client, getCurrentRound, roundParam) => {
const getRoundDetails = async (req, res, client, getCurrentRound, roundParam, moduleId) => {
if (roundParam === 'current') {
const { meridianContractAddress, meridianRoundIndex } = getCurrentRound()
const { meridianContractAddresses, meridianRoundIndexes } = getCurrentRound()
const addr = encodeURIComponent(meridianContractAddress)
const idx = encodeURIComponent(meridianRoundIndex)
const location = `/rounds/meridian/${addr}/${idx}`
Expand Down Expand Up @@ -291,18 +233,3 @@ export const createHandler = async ({
})
}
}

/**
* Parse a date string field that may be `undefined` or `null`.
*
* - undefined -> undefined
* - null -> undefined
* - "iso-date-string" -> new Date("iso-date-string")
*
* @param {string | null | undefined} str
* @returns {Date | undefined}
*/
const parseOptionalDate = (str) => {
if (str === undefined || str === null) return undefined
return new Date(str)
}
6 changes: 3 additions & 3 deletions lib/ie-contract.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ethers } from 'ethers'
import { IE_CONTRACT_ABI, IE_CONTRACT_ADDRESS, RPC_URL, GLIF_TOKEN } from '../spark-publish/ie-contract-config.js'
import { IE_CONTRACT_ABI, RPC_URL, GLIF_TOKEN } from '../spark-publish/ie-contract-config.js'

const provider = new ethers.providers.JsonRpcProvider({
url: RPC_URL,
Expand All @@ -11,8 +11,8 @@ const provider = new ethers.providers.JsonRpcProvider({
// Uncomment for troubleshooting
// provider.on('debug', d => console.log('[ethers:debug] %s\nrequest: %o\nresponse: %o', d.action, d.request, d.response))

export const createMeridianContract = async () => new ethers.Contract(
IE_CONTRACT_ADDRESS,
export const createMeridianContract = address => new ethers.Contract(
address,
IE_CONTRACT_ABI,
provider
)
Loading