Skip to content

Commit

Permalink
Merge pull request #64 from freespek/igor/list
Browse files Browse the repository at this point in the history
add a simple implementation of `solarkraft list`
  • Loading branch information
konnov authored May 17, 2024
2 parents c900229 + 04e52dc commit 5096a6b
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 19 deletions.
54 changes: 53 additions & 1 deletion solarkraft/src/fetcher/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@
import JSONbigint from 'json-bigint'
import { OrderedMap } from 'immutable'
import { join } from 'node:path/posix'
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import {
existsSync,
mkdirSync,
readFileSync,
readdirSync,
writeFileSync,
} from 'node:fs'

const JSONbig = JSONbigint({ useNativeBigInt: true })

Expand Down Expand Up @@ -68,6 +74,16 @@ export interface ContractCallEntry {
oldFields: FieldsMap
}

/**
* A listing entry.
*/
export interface ListEntry {
contractId: string
height: number
txHash: string
verification: 'ok' | 'fail' | 'unverified'
}

/**
* Serializable fetcher state.
*/
Expand Down Expand Up @@ -128,6 +144,42 @@ export function loadContractCallEntry(filename: string): ContractCallEntry {
}
}

/**
* Generate storage entries for a given contract id in a path.
* @param contractId contract identifier (address)
* @param path the path to the contract storage
*/
export function* yieldListEntriesForContract(
contractId: string,
path: string
): Generator<ListEntry> {
for (const dirent of readdirSync(path, { withFileTypes: true })) {
// match ledger heights, which are positive integers
if (dirent.isDirectory() && /^[0-9]+$/.exec(dirent.name)) {
// This directory may contain several transactions for the same height.
const height = Number.parseInt(dirent.name)
for (const ledgerDirent of readdirSync(join(path, dirent.name), {
withFileTypes: true,
})) {
// match all storage entries, which may be reported in different cases
const matcher = /^entry-([0-9a-fA-F]+)\.json$/.exec(
ledgerDirent.name
)
if (ledgerDirent.isFile() && matcher) {
const txHash = matcher[1]
// TODO: read the verification result and report it
yield {
contractId,
height,
txHash,
verification: 'unverified',
}
}
}
}
}
}

/**
* Load fetcher state from the storage.
* @param root the storage root directory
Expand Down
59 changes: 42 additions & 17 deletions solarkraft/src/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,49 @@
* @license
* [Apache-2.0](https://github.com/freespek/solarkraft/blob/main/LICENSE)
*/

/**
* List the transactions fetched from the ledger
* List the transactions in the storage.
*
* Igor Konnov, 2024.
*/
export function list() {
console.log(
`*** WARNING: THIS IS A MOCK. NOTHING IS IMPLEMENTED YET. ***\n`
)

console.log(`Verified:`)
console.log(
`TX d669f322d1011a3726301535f5451ef4398d40ad150b79845fb9a5fc781092cf at 51291024`
)
console.log(
`TX cf96fc2bb266912f8063c6091a70a680498bbe41e71132814f765186926e4f80 at 51291018`
)
console.log('')
console.log(`Unverified:`)
console.log(
`TX b6efc58ab03db82b5b2fa44b69ff89b64e54af5e6e5266dbdd70fc57d2dc583e at 51291021`
)
import { existsSync, readdirSync } from 'fs'
import { storagePath, yieldListEntriesForContract } from './fetcher/storage.js'
import { join } from 'node:path'

// the length of a contract id in its string representation
const CONTRACT_ID_LENGTH = 56

/**
* List the transactions fetched from the ledger.
*/
export function list(args: any) {
// Read from the file system directly.
// In the future versions, we may want to introduce an abstraction in fetcher/storage.ts.
const storageRoot = storagePath(args.home)
if (!existsSync(storageRoot)) {
console.log(`The storage is empty. Run 'solarkraft fetch'`)
return
}

readdirSync(storageRoot, { withFileTypes: true }).map((dirent) => {
if (dirent.isDirectory() && dirent.name.length === CONTRACT_ID_LENGTH) {
// this is a storage directory for a contract
if (args.id === '' || dirent.name === args.id) {
console.log(`Contract ${dirent.name}:`)
console.log('')

for (const e of yieldListEntriesForContract(
dirent.name,
join(storageRoot, dirent.name)
)) {
console.log(` [${e.verification}]`)
console.log(` height: ${e.height}`)
console.log(` tx: ${e.txHash}`)
console.log('')
}
}
}
})
}
8 changes: 7 additions & 1 deletion solarkraft/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,13 @@ const verifyCmd = {
const listCmd = {
command: ['list'],
desc: 'list the fetched and verified transactions',
builder: (yargs: any) => defaultOpts(yargs),
builder: (yargs: any) =>
defaultOpts(yargs).option('id', {
desc: 'Contract id',
type: 'string',
default: '',
require: false,
}),
handler: list,
}

Expand Down
40 changes: 40 additions & 0 deletions solarkraft/test/e2e/list.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Integration tests for the `verify` command

import { describe, it } from 'mocha'

import { spawn } from 'nexpect'
import { mkdtempSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { saveContractCallEntry } from '../../src/fetcher/storage.js'
import { OrderedMap } from 'immutable'

const TX_HASH =
'9fb12935fbadcd28aa220d076f11be631590d22c60977a53997a746898322ca3'
const CONTRACT_ID = 'CC22QGTOUMERDNIYN7TPNX3V6EMPHQXVSRR3XY56EADF7YTFISD2ROND'

describe('list', () => {
it('lists a single entry', function (done) {
// create a single entry in a temporary directory
const root = join(tmpdir(), 'solarkraft-storage-')
mkdtempSync(root)
saveContractCallEntry(root, {
height: 1000,
txHash: TX_HASH,
contractId: CONTRACT_ID,
method: 'set_i32',
methodArgs: [42],
returnValue: 0,
fields: OrderedMap<string, any>(),
oldFields: OrderedMap<string, any>(),
})

this.timeout(50000)
spawn(`solarkraft list --home ${root}`)
.wait(`Contract ${CONTRACT_ID}`)
.wait(' [unverified]')
.wait(' height: 1000')
.wait(` tx: ${TX_HASH}`)
.run(done)
})
})

0 comments on commit 5096a6b

Please sign in to comment.