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

add a simple implementation of solarkraft list #64

Merged
merged 4 commits into from
May 17, 2024
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
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)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add a comment explaining this regex

// 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(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we using mixed-case HEX here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would be ok with lowercase, but neither Windows, nor MacOS agree with me on the capitalization of filenames

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah right, OS jank

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added a comment

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)
})
})
Loading