Skip to content

Commit

Permalink
move decoder into core (#367)
Browse files Browse the repository at this point in the history
* move decoder into core

* fix import

* use map instead
  • Loading branch information
ermalkaleci committed Aug 11, 2023
1 parent 8507626 commit c848149
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 107 deletions.
2 changes: 1 addition & 1 deletion packages/chopsticks/src/plugins/decode-key/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Config } from '../../schema'
import { HexString } from '@polkadot/util/types'
import { decodeKey } from '../../utils/decoder'
import { decodeKey } from '@acala-network/chopsticks-core'
import { defaultOptions } from '../../cli-options'
import { setupContext } from '../../context'
import type yargs from 'yargs'
Expand Down
6 changes: 3 additions & 3 deletions packages/chopsticks/src/plugins/dry-run/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ export const rpc: Handler = async (context, [params]) => {
if (raw) {
return storageDiff
}
const [oldData, newData, delta] = await decodeStorageDiff(context.chain.head, storageDiff)
const { oldState, newState, delta } = await decodeStorageDiff(context.chain.head, storageDiff)
return {
old: oldData,
new: newData,
old: oldState,
new: newState,
delta,
}
}
103 changes: 3 additions & 100 deletions packages/chopsticks/src/utils/decoder.ts
Original file line number Diff line number Diff line change
@@ -1,115 +1,18 @@
import '@polkadot/types-codec'
import { Block } from '@acala-network/chopsticks-core'
import { DecoratedMeta } from '@polkadot/types/metadata/decorate/types'
import { Block, decodeBlockStorageDiff } from '@acala-network/chopsticks-core'
import { HexString } from '@polkadot/util/types'
import { StorageEntry } from '@polkadot/types/primitive/types'
import { StorageKey } from '@polkadot/types'
import { blake2AsHex } from '@polkadot/util-crypto'
import { create } from 'jsondiffpatch'
import { hexToU8a, u8aToHex } from '@polkadot/util'
import _ from 'lodash'

const diffPatcher = create({
array: { detectMove: false },
textDiff: { minLength: Number.MAX_VALUE }, // skip text diff
})

const _CACHE: Record<string, Record<HexString, StorageEntry>> = {}

const getCache = (uid: string): Record<HexString, StorageEntry> => {
if (!_CACHE[uid]) {
_CACHE[uid] = {}
}
return _CACHE[uid]
}

const getStorageEntry = (meta: DecoratedMeta, block: Block, key: HexString) => {
const cache = getCache(block.chain.uid)
for (const [prefix, storageEntry] of Object.entries(cache)) {
if (key.startsWith(prefix)) return storageEntry
}
for (const module of Object.values(meta.query)) {
for (const storage of Object.values(module)) {
const keyPrefix = u8aToHex(storage.keyPrefix())
if (key.startsWith(keyPrefix)) {
cache[keyPrefix] = storage
return storage
}
}
}
return undefined
}

export const decodeKey = (
meta: DecoratedMeta,
block: Block,
key: HexString,
): { storage?: StorageEntry; decodedKey?: StorageKey } => {
const storage = getStorageEntry(meta, block, key)
const decodedKey = meta.registry.createType('StorageKey', key)
if (storage) {
decodedKey.setMeta(storage.meta)
return { storage, decodedKey }
}
return {}
}

export const decodeKeyValue = (meta: DecoratedMeta, block: Block, key: HexString, value?: HexString | null) => {
const { storage, decodedKey } = decodeKey(meta, block, key)

if (!storage || !decodedKey) {
return { [key]: value }
}

const decodeValue = () => {
if (!value) return null
if (storage.section === 'substrate' && storage.method === 'code') {
return `:code blake2_256 ${blake2AsHex(value, 256)} (${hexToU8a(value).length} bytes)`
}
return meta.registry.createType(decodedKey.outputType, hexToU8a(value)).toHuman()
}

switch (decodedKey.args.length) {
case 2: {
return {
[storage.section]: {
[storage.method]: {
[decodedKey.args[0].toString()]: {
[decodedKey.args[1].toString()]: decodeValue(),
},
},
},
}
}
case 1: {
return {
[storage.section]: {
[storage.method]: {
[decodedKey.args[0].toString()]: decodeValue(),
},
},
}
}
default:
return {
[storage.section]: {
[storage.method]: decodeValue(),
},
}
}
}

export const decodeStorageDiff = async (block: Block, diff: [HexString, HexString | null][]) => {
const oldState = {}
const newState = {}
const meta = await block.meta
for (const [key, value] of diff) {
_.merge(oldState, decodeKeyValue(meta, block, key, (await block.get(key)) as any))
_.merge(newState, decodeKeyValue(meta, block, key, value))
}
const [oldState, newState] = await decodeBlockStorageDiff(block, diff)
const oldStateWithoutEvents = _.cloneDeep(oldState)
if (oldStateWithoutEvents['system']?.['events']) {
oldStateWithoutEvents['system']['events'] = []
}
return [oldState, newState, diffPatcher.diff(oldStateWithoutEvents, newState)]
return { oldState, newState, delta: diffPatcher.diff(oldStateWithoutEvents, newState) }
}
4 changes: 2 additions & 2 deletions packages/chopsticks/src/utils/generate-html-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { template } from 'lodash'
import path from 'node:path'

export const generateHtmlDiff = async (block: Block, diff: [HexString, HexString | null][]) => {
const [left, _right, delta] = await decodeStorageDiff(block, diff)
const { oldState, delta } = await decodeStorageDiff(block, diff)
const htmlTemplate = readFileSync(path.join(__dirname, '../../template/diff.html'), 'utf-8')
return template(htmlTemplate)({ left: JSON.stringify(left), delta: JSON.stringify(delta) })
return template(htmlTemplate)({ left: JSON.stringify(oldState), delta: JSON.stringify(delta) })
}

export const generateHtmlDiffPreviewFile = async (
Expand Down
111 changes: 111 additions & 0 deletions packages/core/src/utils/decoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import '@polkadot/types-codec'
import { Block } from '../blockchain/block'
import { DecoratedMeta } from '@polkadot/types/metadata/decorate/types'
import { HexString } from '@polkadot/util/types'
import { StorageEntry } from '@polkadot/types/primitive/types'
import { StorageKey } from '@polkadot/types'
import { blake2AsHex } from '@polkadot/util-crypto'
import { hexToU8a, u8aToHex } from '@polkadot/util'
import _ from 'lodash'

const _CACHE: Record<string, Map<HexString, StorageEntry>> = {}

const getCache = (uid: string): Map<HexString, StorageEntry> => {
if (!_CACHE[uid]) {
_CACHE[uid] = new Map()
}
return _CACHE[uid]
}

const getStorageEntry = (meta: DecoratedMeta, block: Block, key: HexString) => {
const cache = getCache(block.chain.uid)
for (const [prefix, storageEntry] of cache.entries()) {
if (key.startsWith(prefix)) return storageEntry
}
for (const module of Object.values(meta.query)) {
for (const storage of Object.values(module)) {
const keyPrefix = u8aToHex(storage.keyPrefix())
if (key.startsWith(keyPrefix)) {
cache.set(keyPrefix, storage)
return storage
}
}
}
return undefined
}

export const decodeKey = (
meta: DecoratedMeta,
block: Block,
key: HexString,
): { storage?: StorageEntry; decodedKey?: StorageKey } => {
const storage = getStorageEntry(meta, block, key)
const decodedKey = meta.registry.createType('StorageKey', key)
if (storage) {
decodedKey.setMeta(storage.meta)
return { storage, decodedKey }
}
return {}
}

export const decodeKeyValue = (meta: DecoratedMeta, block: Block, key: HexString, value?: HexString | null) => {
const { storage, decodedKey } = decodeKey(meta, block, key)

if (!storage || !decodedKey) {
return { [key]: value }
}

const decodeValue = () => {
if (!value) return null
if (storage.section === 'substrate' && storage.method === 'code') {
return `:code blake2_256 ${blake2AsHex(value, 256)} (${hexToU8a(value).length} bytes)`
}
return meta.registry.createType(decodedKey.outputType, hexToU8a(value)).toHuman()
}

switch (decodedKey.args.length) {
case 2: {
return {
[storage.section]: {
[storage.method]: {
[decodedKey.args[0].toString()]: {
[decodedKey.args[1].toString()]: decodeValue(),
},
},
},
}
}
case 1: {
return {
[storage.section]: {
[storage.method]: {
[decodedKey.args[0].toString()]: decodeValue(),
},
},
}
}
default:
return {
[storage.section]: {
[storage.method]: decodeValue(),
},
}
}
}

/**
* Decode block storage diff
* @param block Block to compare storage diff
* @param diff Storage diff
* @returns decoded old state and new state
*/
export const decodeBlockStorageDiff = async (block: Block, diff: [HexString, HexString | null][]) => {
const oldState = {}
const newState = {}
const meta = await block.meta
for (const [key, value] of diff) {
_.merge(oldState, decodeKeyValue(meta, block, key, (await block.get(key)) as any))
_.merge(newState, decodeKeyValue(meta, block, key, value))
}
return [oldState, newState]
}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Blockchain } from '../blockchain'

export * from './set-storage'
export * from './time-travel'
export * from './decoder'

export type GetKeys = (startKey?: string) => Promise<StorageKey<any>[]>

Expand Down
2 changes: 1 addition & 1 deletion packages/e2e/src/decoder.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { afterAll, describe, expect, it } from 'vitest'
import { decodeKey, decodeKeyValue } from '@acala-network/chopsticks/utils/decoder'
import { decodeKey, decodeKeyValue } from '@acala-network/chopsticks-core/utils/decoder'

import networks from './networks'

Expand Down

0 comments on commit c848149

Please sign in to comment.