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

Chopsticks provider #432

Merged
merged 21 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ dist
*.sqlite
*.sqlite-journal
*.wasm
*.db

.DS_store

Expand Down
2 changes: 1 addition & 1 deletion packages/chopsticks/src/plugins/dry-run/rpc.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HexString } from '@polkadot/util/types'
import { z } from 'zod'

import { Context, ResponseError } from '../../rpc/shared'
import { Context, ResponseError } from '@acala-network/chopsticks-core'
import { decodeStorageDiff } from '../../utils/decoder'
import { generateHtmlDiff } from '../../utils/generate-html-diff'

Expand Down
2 changes: 1 addition & 1 deletion packages/chopsticks/src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Handlers } from '@acala-network/chopsticks-core'
import { camelCase } from 'lodash'
import { lstatSync, readdirSync } from 'fs'
import type yargs from 'yargs'

import { Handlers } from '../rpc/shared'
import { defaultLogger } from '../logger'

const logger = defaultLogger.child({ name: 'plugin' })
Expand Down
3 changes: 1 addition & 2 deletions packages/chopsticks/src/plugins/new-block/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Context, ResponseError } from '../../rpc/shared'
import { DownwardMessage, HorizontalMessage } from '@acala-network/chopsticks-core'
import { Context, DownwardMessage, HorizontalMessage, ResponseError } from '@acala-network/chopsticks-core'
import { HexString } from '@polkadot/util/types'
import { defaultLogger } from '../../logger'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { BuildBlockMode } from '@acala-network/chopsticks-core'
import { Context, ResponseError } from '../../rpc/shared'
import { BuildBlockMode, Context, ResponseError } from '@acala-network/chopsticks-core'
import { defaultLogger } from '../../logger'

/**
Expand Down
3 changes: 1 addition & 2 deletions packages/chopsticks/src/plugins/set-head/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Block } from '@acala-network/chopsticks-core'
import { Context, ResponseError } from '../../rpc/shared'
import { Block, Context, ResponseError } from '@acala-network/chopsticks-core'
import { HexString } from '@polkadot/util/types'

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context, ResponseError } from '../../rpc/shared'
import { Context, ResponseError } from '@acala-network/chopsticks-core'
import { defaultLogger } from '../../logger'

/**
Expand Down
3 changes: 1 addition & 2 deletions packages/chopsticks/src/plugins/set-storage/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Context, ResponseError, StorageValues, setStorage } from '@acala-network/chopsticks-core'
import { HexString } from '@polkadot/util/types'

import { Context, ResponseError } from '../../rpc/shared'
import { StorageValues, setStorage } from '@acala-network/chopsticks-core'
import { defaultLogger } from '../../logger'

/**
Expand Down
3 changes: 1 addition & 2 deletions packages/chopsticks/src/plugins/time-travel/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Context, ResponseError } from '../../rpc/shared'
import { timeTravel } from '@acala-network/chopsticks-core'
import { Context, ResponseError, timeTravel } from '@acala-network/chopsticks-core'

/**
* Travel to a specific time.
Expand Down
11 changes: 9 additions & 2 deletions packages/chopsticks/src/rpc/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { Context, Handlers, ResponseError, SubscriptionManager, logger } from './shared'
import {
Context,
Handlers,
ResponseError,
SubscriptionManager,
logger,
substrate,
} from '@acala-network/chopsticks-core'

import { pluginHandlers } from '../plugins'
import substrate from './substrate'

const allHandlers: Handlers = {
...substrate,
Expand Down
2 changes: 1 addition & 1 deletion packages/chopsticks/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ResponseError, SubscriptionManager } from '@acala-network/chopsticks-core'
import WebSocket, { AddressInfo, WebSocketServer } from 'ws'

import { ResponseError, SubscriptionManager } from './rpc/shared'
import { defaultLogger, truncate } from './logger'

const logger = defaultLogger.child({ name: 'ws' })
Expand Down
2 changes: 1 addition & 1 deletion packages/chopsticks/src/setup-with-server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Config } from './schema'
import { createServer } from './server'
import { handler } from './rpc'
import { logger } from './rpc/shared'
import { logger } from '@acala-network/chopsticks-core'
import { setupContext } from './context'
import _ from 'lodash'

Expand Down
11 changes: 8 additions & 3 deletions packages/chopsticks/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
*
* @packageDocumentation
*/
export { ChainProperties, RuntimeVersion } from '@acala-network/chopsticks-core'
export type {
ChainProperties,
RuntimeVersion,
Context,
SubscriptionManager,
Handler,
} from '@acala-network/chopsticks-core'
export * from '@acala-network/chopsticks-core/src/rpc/substrate'
export * from './plugins/types'
export * from './rpc/substrate'
export { Context, SubscriptionManager, Handler } from './rpc/shared'
209 changes: 209 additions & 0 deletions packages/core/src/chopsticks-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { EventEmitter } from 'eventemitter3'
import {
ProviderInterface,
ProviderInterfaceCallback,
ProviderInterfaceEmitCb,
ProviderInterfaceEmitted,
ProviderStats,
} from '@polkadot/rpc-provider/types'
import { truncate } from 'lodash'

import { Blockchain } from './blockchain'
import { allHandlers } from './rpc'
import { defaultLogger } from './logger'
import { setStorage } from './utils'
import { setup } from './setup'

interface SubscriptionHandler {
callback: ProviderInterfaceCallback
type: string
}

interface Subscription extends SubscriptionHandler {
method: string
params: unknown[]
onCancel?: () => void
}

export interface ChopsticksProviderProps {
/** upstream endpoint */
endpoint: string | undefined
qiweiii marked this conversation as resolved.
Show resolved Hide resolved
/** default to latest block */
blockHash?: string
}

/**
* A provider for ApiPromise
*/
export class ChopsticksProvider implements ProviderInterface {
#isConnected = false
#eventemitter: EventEmitter
#isReadyPromise: Promise<void>
#chainPromise: Promise<Blockchain>
#chain: Blockchain | undefined
#endpoint: string
readonly stats?: ProviderStats
#subscriptions: Record<string, Subscription> = {}

constructor({ endpoint, blockHash }: ChopsticksProviderProps) {
if (!endpoint) {
throw new Error('ChopsticksProvider requires the upstream endpoint')
}
this.#endpoint = endpoint
// FIXME: WARNING in /node_modules/typeorm/browser/driver/react-native/ReactNativeDriver.js
// see: https://github.com/typeorm/typeorm/issues/2158
// this repo may not have this problem since using vite, but polkadot.js app will have
this.#chainPromise = setup({
endpoint: endpoint,
mockSignatureHost: true,
db: 'chopsticks.db',
qiweiii marked this conversation as resolved.
Show resolved Hide resolved
block: blockHash,
})

this.#eventemitter = new EventEmitter()

this.connect()

this.#isReadyPromise = new Promise((resolve, reject): void => {
this.#eventemitter.once('connected', (): void => {
resolve()
})
this.#eventemitter.once('error', reject)
})
}

get hasSubscriptions(): boolean {
return true
}

get isClonable(): boolean {
return true
}

get isConnected(): boolean {
return this.#isConnected
}

get isReady(): Promise<void> {
return this.#isReadyPromise
}

get chain() {
return this.#chainPromise
}

clone = (): ProviderInterface => {
return new ChopsticksProvider({ endpoint: this.#endpoint })
}

connect = async (): Promise<void> => {
return this.#chainPromise
.then((chain) => {
this.#chain = chain
return setStorage(chain, {
System: {
Account: [
[
['5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'],
{
providers: 1,
data: {
free: '1000000000000000000',
},
},
],
],
},
})
})
.then(() => {
this.#isConnected = true
this.#eventemitter.emit('connected')
})
}

disconnect = async (): Promise<void> => {
await this.#chain?.api?.disconnect()
await this.#chain?.close()
this.#isConnected = false
this.#eventemitter.emit('disconnected')
}

on = (type: ProviderInterfaceEmitted, sub: ProviderInterfaceEmitCb): (() => void) => {
this.#eventemitter.on(type, sub)

return (): void => {
this.#eventemitter.removeListener(type, sub)
}
}

#subscriptionManager = {
subscribe: (method: string, subid: string, onCancel: () => void = () => {}) => {
if (this.#subscriptions[subid]) this.#subscriptions[subid].onCancel = onCancel
return (data: any) => {
if (this.#subscriptions[subid]) {
defaultLogger.trace({ method, subid, data: truncate(data) }, 'Subscription notification')
this.#subscriptions[subid].callback(null, data)
}
}
},
unsubscribe: (subid: string) => {
if (this.#subscriptions[subid]) {
this.#subscriptions[subid].onCancel?.()
delete this.#subscriptions[subid]
}
},
}

send = async <T>(
method: string,
params: unknown[],
_isCacheable?: boolean,
subscription?: SubscriptionHandler,
): Promise<T> => {
await this.isReady
const handler = allHandlers[method]
if (!handler) {
defaultLogger.error(`Unable to find handler=${method}`)
return Promise.reject(new Error(`Unable to find handler=${method}`))
}
if (subscription) {
const subid = `${subscription.type}::${method}`
this.#subscriptions[subid] = {
callback: subscription.callback,
method,
params,
type: subscription.type,
}
}
defaultLogger.debug({ method, params }, `Calling handler`)
const result = await handler({ chain: this.#chain! }, params, this.#subscriptionManager)
return result
}

subscribe(
type: string,
method: string,
params: unknown[],
callback: ProviderInterfaceCallback,
): Promise<number | string> {
return this.send<string | number>(method, params, false, { callback, type })
}

async unsubscribe(type: string, method: string, id: number | string): Promise<boolean> {
const subscription = `${type}::${id}`

if (!this.#subscriptions[subscription]) {
defaultLogger.debug(`Unable to find active subscription=${subscription}`)
return false
}

delete this.#subscriptions[subscription]

try {
return this.isConnected && this.#chain ? this.send<boolean>(method, [id]) : true
} catch {
return false
}
}
}
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ export * from './setup'
export * from './blockchain/inherent'
export * from './logger'
export * from './offchain'
export * from './chopsticks-provider'
export * from './rpc'
15 changes: 15 additions & 0 deletions packages/core/src/rpc/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Handlers } from './shared'
import substrate from './substrate'

export const allHandlers: Handlers = {
...substrate,
rpc_methods: async () =>
Promise.resolve({
version: 1,
methods: [...Object.keys(allHandlers)],
}),
}

export { default as substrate } from './substrate'
export { ResponseError } from './shared'
export type { Context, SubscriptionManager, Handler, Handlers } from './shared'
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class ResponseError extends Error {

export interface Context {
/**
* The blockchain instance, see `Blockchain` type in the `core` package
* The blockchain instance
*/
chain: Blockchain
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { APPLY_EXTRINSIC_ERROR, Block } from '@acala-network/chopsticks-core'
import { HexString } from '@polkadot/util/types'
import { TransactionValidityError } from '@polkadot/types/interfaces'

import { APPLY_EXTRINSIC_ERROR } from '../../blockchain/txpool'
import { Block } from '../../blockchain/block'
import { Handler, ResponseError, SubscriptionManager } from '../shared'
import { defaultLogger } from '../../logger'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { ChainProperties } from '@acala-network/chopsticks-core'
import { HexString } from '@polkadot/util/types'
import { Index } from '@polkadot/types/interfaces'
import { hexToU8a } from '@polkadot/util'
import { readFileSync } from 'node:fs'
import path from 'node:path'

import { ChainProperties } from '../../api'
import { Handler } from '../shared'

export const system_localPeerId = async () => '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'
Expand All @@ -20,8 +18,7 @@ export const system_name: Handler<void, string> = async (context) => {
return context.chain.api.getSystemName()
}
export const system_version: Handler<void, string> = async (_context) => {
const { version } = JSON.parse(readFileSync(path.join(__dirname, '../../../package.json'), 'utf-8'))
return `chopsticks-v${version}`
return 'chopsticks-v1'
}
export const system_chainType: Handler<void, string> = async (_context) => {
return 'Development'
Expand Down
Loading
Loading