-
Notifications
You must be signed in to change notification settings - Fork 220
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
client utils vstorage without instance binding (#10566)
closes: #10563 ## Description It would help users of client-utils to be able to use VStorage methods as first-class functions. This removes using `this` as the namespace of the helper functions. ### Security Considerations none ### Scaling Considerations none ### Documentation Considerations none ### Testing Considerations New test (which failed before the change) ### Upgrade Considerations none
- Loading branch information
Showing
4 changed files
with
146 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/* global Buffer */ | ||
|
||
/** | ||
* @import {MinimalNetworkConfig} from './rpc.js'; | ||
*/ | ||
|
||
/** | ||
* @param {object} powers | ||
* @param {typeof window.fetch} powers.fetch | ||
* @param {MinimalNetworkConfig} config | ||
*/ | ||
export const makeVStorage = (powers, config) => { | ||
/** @param {string} path */ | ||
const getJSON = path => { | ||
const url = config.rpcAddrs[0] + path; | ||
// console.warn('fetching', url); | ||
return powers.fetch(url, { keepalive: true }).then(res => res.json()); | ||
}; | ||
// height=0 is the same as omitting height and implies the highest block | ||
const url = (path = 'published', { kind = 'children', height = 0 } = {}) => | ||
`/abci_query?path=%22/custom/vstorage/${kind}/${path}%22&height=${height}`; | ||
|
||
const readStorage = (path = 'published', { kind = 'children', height = 0 }) => | ||
getJSON(url(path, { kind, height })) | ||
.catch(err => { | ||
throw Error(`cannot read ${kind} of ${path}: ${err.message}`); | ||
}) | ||
.then(data => { | ||
const { | ||
result: { response }, | ||
} = data; | ||
if (response?.code !== 0) { | ||
/** @type {any} */ | ||
const err = Error( | ||
`error code ${response?.code} reading ${kind} of ${path}: ${response.log}`, | ||
); | ||
err.code = response?.code; | ||
err.codespace = response?.codespace; | ||
throw err; | ||
} | ||
return data; | ||
}); | ||
|
||
const vstorage = { | ||
url, | ||
decode({ result: { response } }) { | ||
const { code } = response; | ||
if (code !== 0) { | ||
throw response; | ||
} | ||
const { value } = response; | ||
return Buffer.from(value, 'base64').toString(); | ||
}, | ||
/** | ||
* | ||
* @param {string} path | ||
* @returns {Promise<string>} latest vstorage value at path | ||
*/ | ||
async readLatest(path = 'published') { | ||
const raw = await readStorage(path, { kind: 'data' }); | ||
return vstorage.decode(raw); | ||
}, | ||
async keys(path = 'published') { | ||
const raw = await readStorage(path, { kind: 'children' }); | ||
return JSON.parse(vstorage.decode(raw)).children; | ||
}, | ||
/** | ||
* @param {string} path | ||
* @param {number} [height] default is highest | ||
* @returns {Promise<{blockHeight: number, values: string[]}>} | ||
*/ | ||
async readAt(path, height = undefined) { | ||
const raw = await readStorage(path, { kind: 'data', height }); | ||
const txt = vstorage.decode(raw); | ||
/** @type {{ value: string }} */ | ||
const { value } = JSON.parse(txt); | ||
return JSON.parse(value); | ||
}, | ||
/** | ||
* Read values going back as far as available | ||
* | ||
* @param {string} path | ||
* @param {number | string} [minHeight] | ||
* @returns {Promise<string[]>} | ||
*/ | ||
async readFully(path, minHeight = undefined) { | ||
const parts = []; | ||
// undefined the first iteration, to query at the highest | ||
let blockHeight; | ||
await null; | ||
do { | ||
// console.debug('READING', { blockHeight }); | ||
let values; | ||
try { | ||
({ blockHeight, values } = await vstorage.readAt( | ||
path, | ||
blockHeight && Number(blockHeight) - 1, | ||
)); | ||
// console.debug('readAt returned', { blockHeight }); | ||
} catch (err) { | ||
if ( | ||
// CosmosSDK ErrInvalidRequest with particular message text; | ||
// misrepresentation of pruned data | ||
// TODO replace after incorporating a fix to | ||
// https://github.com/cosmos/cosmos-sdk/issues/19992 | ||
err.codespace === 'sdk' && | ||
err.code === 18 && | ||
err.message.match(/pruned/) | ||
) { | ||
// console.error(err); | ||
break; | ||
} | ||
throw err; | ||
} | ||
parts.push(values); | ||
// console.debug('PUSHED', values); | ||
// console.debug('NEW', { blockHeight, minHeight }); | ||
if (minHeight && Number(blockHeight) <= Number(minHeight)) break; | ||
} while (blockHeight > 0); | ||
return parts.flat(); | ||
}, | ||
}; | ||
return vstorage; | ||
}; | ||
/** @typedef {ReturnType<typeof makeVStorage>} VStorage */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* eslint-env node */ | ||
// @ts-check | ||
import test from 'ava'; | ||
import { makeVStorage } from '../src/vstorage.js'; | ||
|
||
/** @type {any} */ | ||
const fetch = () => Promise.resolve({}); | ||
|
||
test('readFully can be used without instance binding', async t => { | ||
const vstorage = makeVStorage({ fetch }, { chainName: '', rpcAddrs: [''] }); | ||
const { readFully } = vstorage; | ||
|
||
// Mock implementation to avoid actual network calls | ||
vstorage.readAt = async () => ({ blockHeight: 0, values: ['test'] }); | ||
|
||
// This would throw if readFully required 'this' binding | ||
await t.notThrowsAsync(() => readFully('some/path')); | ||
}); |