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

Odis Combiner 3.0.0 Release #10547

Merged
merged 20 commits into from
Sep 9, 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
7 changes: 4 additions & 3 deletions packages/phone-number-privacy/combiner/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@celo/phone-number-privacy-combiner",
"version": "3.0.0-beta.8-dev",
"version": "3.0.1-dev",
"description": "Orchestrates and combines threshold signatures for use in ODIS",
"author": "Celo",
"license": "Apache-2.0",
Expand All @@ -23,9 +23,10 @@
"test": "jest --runInBand --testPathIgnorePatterns test/end-to-end",
"test:coverage": "yarn test --coverage",
"test:integration": "jest --runInBand test/integration",
"test:e2e": "jest test/end-to-end --verbose",
"test:e2e": "jest --runInBand test/end-to-end --verbose",
"test:e2e:staging": "CONTEXT_NAME=staging yarn test:e2e",
"test:e2e:alfajores": "CONTEXT_NAME=alfajores yarn test:e2e"
"test:e2e:alfajores": "CONTEXT_NAME=alfajores yarn test:e2e",
"test:e2e:mainnet": "CONTEXT_NAME=mainnet yarn test:e2e"
},
"dependencies": {
"@celo/contractkit": "^4.1.2-dev",
Expand Down
7 changes: 6 additions & 1 deletion packages/phone-number-privacy/combiner/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface OdisConfig {
fullNodeTimeoutMs: number
fullNodeRetryCount: number
fullNodeRetryDelayMs: number
shouldAuthenticate: boolean
}

export interface CombinerConfig {
Expand Down Expand Up @@ -106,6 +107,7 @@ if (DEV_MODE) {
fullNodeTimeoutMs: FULL_NODE_TIMEOUT_IN_MS,
fullNodeRetryCount: RETRY_COUNT,
fullNodeRetryDelayMs: RETRY_DELAY_IN_MS,
shouldAuthenticate: true,
},
domains: {
serviceName: defaultServiceName,
Expand Down Expand Up @@ -140,6 +142,7 @@ if (DEV_MODE) {
fullNodeTimeoutMs: FULL_NODE_TIMEOUT_IN_MS,
fullNodeRetryCount: RETRY_COUNT,
fullNodeRetryDelayMs: RETRY_DELAY_IN_MS,
shouldAuthenticate: true,
},
}
} else {
Expand Down Expand Up @@ -168,6 +171,7 @@ if (DEV_MODE) {
fullNodeRetryDelayMs: Number(
functionConfig.pnp.full_node_retry_delay_ms ?? RETRY_DELAY_IN_MS
),
shouldAuthenticate: toBool(functionConfig.pnp.should_authenticate, true),
},
domains: {
serviceName: functionConfig.domains.service_name ?? defaultServiceName,
Expand All @@ -182,11 +186,12 @@ if (DEV_MODE) {
currentVersion: Number(functionConfig.domains_keys.current_version),
versions: functionConfig.domains_keys.versions,
},
fullNodeTimeoutMs: Number(functionConfig.pnp.full_node_timeout_ms ?? FULL_NODE_TIMEOUT_IN_MS),
fullNodeTimeoutMs: Number(functionConfig.pnp.full_node_timeout_ms ?? FULL_NODE_TIMEOUT_IN_MS), // TODO refactor config - domains endpoints don't use full node
fullNodeRetryCount: Number(functionConfig.pnp.full_node_retry_count ?? RETRY_COUNT),
fullNodeRetryDelayMs: Number(
functionConfig.pnp.full_node_retry_delay_ms ?? RETRY_DELAY_IN_MS
),
shouldAuthenticate: true,
},
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/phone-number-privacy/combiner/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const combiner = functions
// Keep instances warm for mainnet functions
// Defined check required for running tests vs. deployment
minInstances: functions.config().service ? Number(functions.config().service.min_instances) : 0,
memory: functions.config().service ? functions.config().service.memory : '512MB',
})
.https.onRequest(startCombiner(config))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ export function pnpQuota(
return errorResult(400, WarningMessage.INVALID_INPUT)
}

if (!(await authenticateUser(request, logger, dekFetcher))) {
return errorResult(401, WarningMessage.UNAUTHENTICATED_USER)
if (config.shouldAuthenticate) {
if (!(await authenticateUser(request, logger, dekFetcher))) {
return errorResult(401, WarningMessage.UNAUTHENTICATED_USER)
}
}

// TODO remove this, we shouldn't need keyVersionInfo for non-signing endpoints
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ export function pnpSign(
return errorResult(400, WarningMessage.INVALID_KEY_VERSION_REQUEST)
}

if (!(await authenticateUser(request, logger, dekFetcher))) {
return errorResult(401, WarningMessage.UNAUTHENTICATED_USER)
if (config.shouldAuthenticate) {
if (!(await authenticateUser(request, logger, dekFetcher))) {
return errorResult(401, WarningMessage.UNAUTHENTICATED_USER)
}
}

const keyVersionInfo = getKeyVersionInfo(request, config, logger)
const crypto = new BLSCryptographyClient(keyVersionInfo)

Expand Down
58 changes: 36 additions & 22 deletions packages/phone-number-privacy/combiner/test/end-to-end/pnp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
SignMessageRequest,
SignMessageResponseSchema,
} from '@celo/phone-number-privacy-common'
import { normalizeAddressWith0x } from '@celo/utils/lib/address'
import threshold_bls from 'blind-threshold-bls'
import { randomBytes } from 'crypto'
import 'isomorphic-fetch'
Expand All @@ -19,10 +20,12 @@ import {
ACCOUNT_ADDRESS_NO_QUOTA,
BLINDED_PHONE_NUMBER,
dekAuthSigner,
deks,
getTestContextName,
PHONE_NUMBER,
walletAuthSigner,
} from './resources'
import { sleep } from '@celo/base'

const { IdentifierPrefix } = OdisUtils.Identifier

Expand All @@ -36,9 +39,17 @@ const fullNodeUrl = process.env.ODIS_BLOCKCHAIN_PROVIDER

const expectedVersion = getCombinerVersion()

// TODO fix combiner e2e tests

describe(`Running against service deployed at ${combinerUrl} w/ blockchain provider ${fullNodeUrl}`, () => {
beforeAll(async () => {
const accounts = await walletAuthSigner.contractKit.contracts.getAccounts()
const dekPublicKey = normalizeAddressWith0x(deks[0].publicKey)
if ((await accounts.getDataEncryptionKey(ACCOUNT_ADDRESS)) !== dekPublicKey) {
await accounts
.setAccountDataEncryptionKey(dekPublicKey)
.sendAndWaitForReceipt({ from: ACCOUNT_ADDRESS })
}
})

it('Service is deployed at correct version', async () => {
const response = await fetch(combinerUrl + CombinerEndpoint.STATUS, {
method: 'GET',
Expand Down Expand Up @@ -140,27 +151,30 @@ describe(`Running against service deployed at ${combinerUrl} w/ blockchain provi
})

describe(`${CombinerEndpoint.PNP_SIGN}`, () => {
describe('new requests', () => {
beforeAll(async () => {
// Replenish quota for ACCOUNT_ADDRESS
// If this fails, may be necessary to faucet ACCOUNT_ADDRESS more funds
const numQueriesToReplenish = 2
const amountInWei = signerConfig.quota.queryPriceInCUSD
.times(1e18)
.times(numQueriesToReplenish)
.toString()
const stableToken = await walletAuthSigner.contractKit.contracts.getStableToken(
StableToken.cUSD
)
const odisPayments = await walletAuthSigner.contractKit.contracts.getOdisPayments()
await stableToken
.approve(odisPayments.address, amountInWei)
.sendAndWaitForReceipt({ from: ACCOUNT_ADDRESS })
await odisPayments
.payInCUSD(ACCOUNT_ADDRESS, amountInWei)
.sendAndWaitForReceipt({ from: ACCOUNT_ADDRESS })
})
beforeAll(async () => {
// Replenish quota for ACCOUNT_ADDRESS
// If this fails, may be necessary to faucet ACCOUNT_ADDRESS more funds
const numQueriesToReplenish = 100
const amountInWei = signerConfig.quota.queryPriceInCUSD
.times(1e18)
.times(numQueriesToReplenish)
.toString()
const stableToken = await walletAuthSigner.contractKit.contracts.getStableToken(
StableToken.cUSD
)
const odisPayments = await walletAuthSigner.contractKit.contracts.getOdisPayments()
await stableToken
.approve(odisPayments.address, amountInWei)
.sendAndWaitForReceipt({ from: ACCOUNT_ADDRESS })
await odisPayments
.payInCUSD(ACCOUNT_ADDRESS, amountInWei)
.sendAndWaitForReceipt({ from: ACCOUNT_ADDRESS })
// wait for cache to expire and then query to refresh
await sleep(5 * 1000)
await OdisUtils.Quota.getPnpQuotaStatus(ACCOUNT_ADDRESS, dekAuthSigner(0), SERVICE_CONTEXT)
})

describe('new requests', () => {
// Requests made for PHONE_NUMBER from ACCOUNT_ADDRESS & same blinding factor
// are replayed from previous test runs (for every run after the very first)
let startingPerformedQueryCount: number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export const getTestContextName = (): OdisContextName => {
export const DEFAULT_FORNO_URL =
process.env.ODIS_BLOCKCHAIN_PROVIDER ?? 'https://alfajores-forno.celo-testnet.org'

export const PRIVATE_KEY = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
export const ACCOUNT_ADDRESS = normalizeAddressWith0x(privateKeyToAddress(PRIVATE_KEY)) // 0x1be31a94361a391bbafb2a4ccd704f57dc04d4bb
export const PRIVATE_KEY = '2c63bf6d60b16c8afa13e1069dbe92fef337c23855fff8b27732b3e9c6e7efd4'
export const ACCOUNT_ADDRESS = normalizeAddressWith0x(privateKeyToAddress(PRIVATE_KEY)) // 0x6037800e91eaa703e38bad40c01410bbdf0fea7e

export const PRIVATE_KEY_NO_QUOTA =
'0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890000000'
Expand Down
12 changes: 7 additions & 5 deletions packages/phone-number-privacy/monitor/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export const queryOdisForSalt = async (
contextName: OdisContextName,
timeoutMs: number = 10000,
bypassQuota: boolean = false,
useDEK: boolean = false
useDEK: boolean = false,
privateKey?: string
) => {
let authSigner: AuthSigner
let accountAddress: string
Expand All @@ -43,12 +44,12 @@ export const queryOdisForSalt = async (

if (useDEK) {
accountAddress = ACCOUNT_ADDRESS
contractKit.connection.addAccount(PRIVATE_KEY)
contractKit.connection.addAccount(privateKey ?? PRIVATE_KEY)
contractKit.defaultAccount = accountAddress
authSigner = dekAuthSigner(0)
phoneNumber = generateRandomPhoneNumber()
} else {
const privateKey = await newPrivateKey()
privateKey ??= await newPrivateKey()
accountAddress = normalizeAddressWith0x(privateKeyToAddress(privateKey))
contractKit.connection.addAccount(privateKey)
contractKit.defaultAccount = accountAddress
Expand Down Expand Up @@ -90,15 +91,16 @@ export const queryOdisForSalt = async (
export const queryOdisForQuota = async (
blockchainProvider: string,
contextName: OdisContextName,
timeoutMs: number = 10000
timeoutMs: number = 10000,
privateKey?: string
) => {
console.log(`contextName: ${contextName}`) // tslint:disable-line:no-console
console.log(`blockchain provider: ${blockchainProvider}`) // tslint:disable-line:no-console

const serviceContext = getServiceContext(contextName, OdisAPI.PNP)

const contractKit = newKit(blockchainProvider, new LocalWallet())
const privateKey = await newPrivateKey()
privateKey ??= await newPrivateKey()
const accountAddress = normalizeAddressWith0x(privateKeyToAddress(privateKey))
contractKit.connection.addAccount(privateKey)
contractKit.defaultAccount = accountAddress
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ yargs
type: 'number',
description: 'number of requests to use when calculating latency moving average',
default: 50,
})
.option('privateKey', {
type: 'string',
description: 'optional private key to send requests from',
}),
(args) => {
if (args.rps == null || args.contextName == null) {
Expand Down Expand Up @@ -82,7 +86,8 @@ yargs
args.duration,
args.bypassQuota,
args.useDEK,
args.movingAvgRequests
args.movingAvgRequests,
args.privateKey
) // tslint:disable-line:no-floating-promises
}
).argv
26 changes: 19 additions & 7 deletions packages/phone-number-privacy/monitor/src/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@ export async function testPNPSignQuery(
contextName: OdisContextName,
timeoutMs?: number,
bypassQuota?: boolean,
useDEK?: boolean
useDEK?: boolean,
privateKey?: string
) {
try {
const odisResponse: IdentifierHashDetails = await queryOdisForSalt(
blockchainProvider,
contextName,
timeoutMs,
bypassQuota,
useDEK
useDEK,
privateKey
)
logger.debug({ odisResponse }, 'ODIS salt request successful. System is healthy.')
} catch (err) {
Expand All @@ -43,14 +45,16 @@ export async function testPNPSignQuery(
export async function testPNPQuotaQuery(
blockchainProvider: string,
contextName: OdisContextName,
timeoutMs?: number
timeoutMs?: number,
privateKey?: string
) {
logger.info(`Performing test PNP query for ${CombinerEndpointPNP.PNP_QUOTA}`)
try {
const odisResponse: PnpClientQuotaStatus = await queryOdisForQuota(
blockchainProvider,
contextName,
timeoutMs
timeoutMs,
privateKey
)
logger.info({ odisResponse }, 'ODIS quota request successful. System is healthy.')
} catch (err) {
Expand Down Expand Up @@ -88,7 +92,8 @@ export async function concurrentRPSLoadTest(
duration: number = 0,
bypassQuota: boolean = false,
useDEK: boolean = false,
movingAverageRequests: number = 50
movingAverageRequests: number = 50,
privateKey?: string
) {
const latencyQueue: number[] = []
let movingAvgLatencySum = 0
Expand Down Expand Up @@ -128,8 +133,15 @@ export async function concurrentRPSLoadTest(
const testFn = async () => {
try {
await (endpoint === CombinerEndpointPNP.PNP_SIGN
? testPNPSignQuery(blockchainProvider, contextName, undefined, bypassQuota, useDEK)
: testPNPQuotaQuery(blockchainProvider, contextName))
? testPNPSignQuery(
blockchainProvider,
contextName,
undefined,
bypassQuota,
useDEK,
privateKey
)
: testPNPQuotaQuery(blockchainProvider, contextName, undefined, privateKey))
} catch (_) {
logger.error('load test request failed')
}
Expand Down