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

option to pre-define or override state #32

Merged
merged 3 commits into from
Oct 31, 2022
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
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,32 @@ Make sure you have setup Rust environment (>= 1.64).
- Use option `--output-path=<file_path>` to print out JSON file

- Run a test node

- `yarn start dev --endpoint=wss://acala-rpc-2.aca-api.network/ws`
- You have a test node running at `ws://localhost:8000`
- You can use [Polkadot.js Apps](https://polkadot.js.org/apps/) to connect to this node
- Submit any transaction to produce a new block in the in parallel reality
- (Optional) Pre-define/override state using option `--state-path=state.json`. See example state below.

```json
{
"Sudo": {
"Key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
},
"TechnicalCommittee": {
"Members": ["5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"]
},
"Tokens": {
"Accounts": [
[
["5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", { "token": "KAR" }],
{
"free": 1000000000000000,
"reserved": 0,
"frozen": 0
}
]
]
}
}
```
8 changes: 4 additions & 4 deletions e2e/__snapshots__/dev.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Vitest Snapshot v1

exports[`dev rpc > setStroages 1`] = `"5F98oWfz2r5rcRVnP9VCndg33DAAsky3iuoBSpaPUbgN9AJn"`;
exports[`dev rpc > setStorages 1`] = `"5F98oWfz2r5rcRVnP9VCndg33DAAsky3iuoBSpaPUbgN9AJn"`;

exports[`dev rpc > setStroages 2`] = `"5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu"`;
exports[`dev rpc > setStorages 2`] = `"5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu"`;

exports[`dev rpc > setStroages 3`] = `
exports[`dev rpc > setStorages 3`] = `
{
"consumers": 0,
"data": {
Expand All @@ -19,7 +19,7 @@ exports[`dev rpc > setStroages 3`] = `
}
`;

exports[`dev rpc > setStroages 4`] = `
exports[`dev rpc > setStorages 4`] = `
{
"consumers": 0,
"data": {
Expand Down
2 changes: 1 addition & 1 deletion e2e/dev.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { u8aToHex } from '@polkadot/util'
import { api, dev, expectJson, testingPairs } from './helper'

describe('dev rpc', () => {
it('setStroages', async () => {
it('setStorages', async () => {
const { alice, test1 } = testingPairs()

await expectJson(api.query.sudo.key()).toMatchSnapshot()
Expand Down
16 changes: 8 additions & 8 deletions src/blockchain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { defaultLogger } from '../logger'
const logger = defaultLogger.child({ name: 'blockchain' })

export class Blockchain {
readonly #api: ApiPromise
readonly api: ApiPromise
readonly tasks: TaskManager
readonly #txpool: TxPool

Expand All @@ -32,7 +32,7 @@ export class Blockchain {
inherentProvider: InherentProvider,
header: { number: number; hash: string }
) {
this.#api = api
this.api = api
this.tasks = tasks
this.#head = new Block(api, this, header.number, header.hash)
this.#registerBlock(this.#head)
Expand All @@ -59,8 +59,8 @@ export class Blockchain {
return undefined
}
if (!this.#blocksByNumber[number]) {
const hash = await this.#api.rpc.chain.getBlockHash(number)
const block = new Block(this.#api, this, number, hash.toHex())
const hash = await this.api.rpc.chain.getBlockHash(number)
const block = new Block(this.api, this, number, hash.toHex())
this.#registerBlock(block)
}
return this.#blocksByNumber[number]
Expand All @@ -72,8 +72,8 @@ export class Blockchain {
}
if (!this.#blocksByHash[hash]) {
try {
const header = await this.#api.rpc.chain.getHeader(hash)
const block = new Block(this.#api, this, header.number.toNumber(), hash)
const header = await this.api.rpc.chain.getHeader(hash)
const block = new Block(this.api, this, header.number.toNumber(), hash)
this.#registerBlock(block)
} catch (e) {
logger.debug(`getBlock(${hash}) failed: ${e}`)
Expand All @@ -90,7 +90,7 @@ export class Blockchain {
Math.round(Math.random() * 100000000)
.toString(16)
.padEnd(64, '0')
const block = new Block(this.#api, this, number, hash, parent, { header, extrinsics: [], storage: parent.storage })
const block = new Block(this.api, this, number, hash, parent, { header, extrinsics: [], storage: parent.storage })
this.#blocksByHash[hash] = block
return block
}
Expand Down Expand Up @@ -122,7 +122,7 @@ export class Blockchain {
const source = '0x02' // External
const args = u8aToHex(u8aConcat(source, extrinsic, this.head.hash))
const res = await this.head.call('TaggedTransactionQueue_validate_transaction', args)
const validity: TransactionValidity = this.#api.createType('TransactionValidity', res.result)
const validity: TransactionValidity = this.api.createType('TransactionValidity', res.result)
if (validity.isOk) {
this.#txpool.submitExtrinsic(extrinsic)
return blake2AsHex(extrinsic, 256)
Expand Down
16 changes: 15 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import { SetTimestamp } from './blockchain/inherents'
import { TaskManager } from './task'
import { createServer } from './server'
import { defaultLogger } from './logger'
import { existsSync, readFileSync, writeFileSync } from 'fs'
import { handler } from './rpc'
import { writeFileSync } from 'fs'
import { setStorage } from './utils/set-storage'
import assert from 'assert'

const setup = async (argv: any) => {
const port = argv.port || process.env.PORT || 8000
Expand Down Expand Up @@ -42,6 +44,14 @@ const setup = async (argv: any) => {

tasks.updateListeningPort(listeningPort)

const statePath = argv['state-path']
if (statePath) {
assert(existsSync(statePath), 'Invalid state path')
const state = JSON.parse(String(readFileSync(statePath)))
defaultLogger.trace({ state }, 'SetStorage')
await setStorage(chain, state)
}

return context
}

Expand Down Expand Up @@ -141,6 +151,10 @@ yargs(hideBin(process.argv))
desc: 'Build block mode. Default to Batch',
enum: [BuildBlockMode.Batch, BuildBlockMode.Manual, BuildBlockMode.Instant],
},
'state-path': {
desc: 'Pre-defined JSON state file path',
string: true,
},
}),
(argv) => {
setup(argv).catch((err) => {
Expand Down
15 changes: 11 additions & 4 deletions src/rpc/substrate/author.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,18 @@ const handlers: Handlers = {
unsubscribe(id)
}

context.chain.submitExtrinsic(extrinsic).then(() => {
callback({
Ready: null,
context.chain
.submitExtrinsic(extrinsic)
.then(() => {
callback({
Ready: null,
})
})
.catch((error) => {
logger.error({ error }, 'ExtrinsicFailed')
callback({ Invalid: null })
unsubscribe(id)
})
})
return id
},
author_unwatchExtrinsic: async (_context, [subid], { unsubscribe }) => {
Expand Down
File renamed without changes.
79 changes: 79 additions & 0 deletions src/utils/set-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { ApiPromise } from '@polkadot/api'
import { Metadata, StorageKey } from '@polkadot/types'
import { Registry } from '@polkadot/types/types'
import { StorageEntryMetadataLatest } from '@polkadot/types/interfaces'
import { createFunction } from '@polkadot/types/metadata/decorate/storage/createFunction'
import assert from 'assert'

import { Blockchain } from '../blockchain'

interface StorageKeyMaker {
meta: StorageEntryMetadataLatest
makeKey: (...keys: any[]) => StorageKey
}

const storageKeyMaker =
(registry: Registry, metadata: Metadata) =>
(section: string, method: string): StorageKeyMaker => {
const pallet = metadata.asLatest.pallets.filter((x) => x.name.toString() === section)[0]
assert(pallet)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should throw error with some description to indicate bad parameter

const meta = pallet.storage
.unwrap()
.items.filter((x) => x.name.toString() === method)[0] as any as StorageEntryMetadataLatest
assert(meta)

const storageFn = createFunction(
registry,
{
meta,
prefix: section,
section,
method,
},
{}
)

return {
meta,
makeKey: (...keys: any[]): StorageKey => new StorageKey(registry, [storageFn, keys]),
}
}

function objectToStorageItems(
api: ApiPromise,
storage: Record<string, Record<string, any | [any, any][]>>
): [string, string | null][] {
const storageItems: [string, string | null][] = []
for (const sectionName in storage) {
const section = storage[sectionName]
for (const storageName in section) {
const storage = section[storageName]
const { makeKey, meta } = storageKeyMaker(api.registry, api.runtimeMetadata)(sectionName, storageName)
if (meta.type.isPlain) {
const key = makeKey()
storageItems.push([key.toHex(), storage ? api.createType(key.outputType, storage).toHex(true) : null])
} else {
for (const [keys, value] of storage) {
const key = makeKey(...keys)
storageItems.push([key.toHex(), value ? api.createType(key.outputType, value).toHex(true) : null])
}
}
}
}
return storageItems
}

export const setStorage = async (
chain: Blockchain,
storage: [string, string][] | Record<string, Record<string, any | Record<string, any>>>
): Promise<void> => {
let storageItems: [string, string | null][]
if (Array.isArray(storage)) {
storageItems = storage
} else {
storageItems = objectToStorageItems(chain.api, storage)
}
const block = await chain.getBlock()
assert(block)
block.pushStorageLayer().setAll(storageItems)
}