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

client utils vstorage without instance binding #10566

Merged
merged 4 commits into from
Nov 26, 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
1 change: 1 addition & 0 deletions packages/client-utils/src/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './rpc.js';
export * from './sync-tools.js';
export * from './vstorage.js';
export * from './vstorage-kit.js';
export * from './wallet-utils.js';

Expand Down
122 changes: 2 additions & 120 deletions packages/client-utils/src/vstorage-kit.js
Original file line number Diff line number Diff line change
@@ -1,135 +1,17 @@
/* global Buffer */
import {
boardSlottingMarshaller,
makeBoardRemote,
} from '@agoric/vats/tools/board-utils.js';
import { makeVStorage } from './vstorage.js';

export { boardSlottingMarshaller };

/**
* @import {MinimalNetworkConfig} from './rpc.js';
* @import {TypedPublished} from './types.js';
* @import {VStorage} from './vstorage.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;
});

return {
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 this.decode(raw);
},
async keys(path = 'published') {
const raw = await readStorage(path, { kind: 'children' });
return JSON.parse(this.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 = this.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 this.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();
},
};
};
/** @typedef {ReturnType<typeof makeVStorage>} VStorage */

/** @deprecated */
export const makeFromBoard = () => {
const cache = new Map();
Expand Down
125 changes: 125 additions & 0 deletions packages/client-utils/src/vstorage.js
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 */
18 changes: 18 additions & 0 deletions packages/client-utils/test/vstorage.test.js
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'));
});
Loading