@agoric/casting 0.4.3-dev-648d42f.0 → 0.4.3-dev-dfc712f.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/casting",
3
- "version": "0.4.3-dev-648d42f.0+648d42f",
3
+ "version": "0.4.3-dev-dfc712f.0+dfc712f",
4
4
  "description": "Agoric's OCap broadcasting system",
5
5
  "type": "module",
6
6
  "main": "src/main.js",
@@ -22,10 +22,10 @@
22
22
  "author": "Agoric",
23
23
  "license": "Apache-2.0",
24
24
  "dependencies": {
25
- "@agoric/internal": "0.3.3-dev-648d42f.0+648d42f",
26
- "@agoric/notifier": "0.6.3-dev-648d42f.0+648d42f",
27
- "@agoric/spawner": "0.6.9-dev-648d42f.0+648d42f",
28
- "@agoric/store": "0.9.3-dev-648d42f.0+648d42f",
25
+ "@agoric/internal": "0.3.3-dev-dfc712f.0+dfc712f",
26
+ "@agoric/notifier": "0.6.3-dev-dfc712f.0+dfc712f",
27
+ "@agoric/spawner": "0.6.9-dev-dfc712f.0+dfc712f",
28
+ "@agoric/store": "0.9.3-dev-dfc712f.0+dfc712f",
29
29
  "@cosmjs/encoding": "^0.30.1",
30
30
  "@cosmjs/proto-signing": "^0.30.1",
31
31
  "@cosmjs/stargate": "^0.30.1",
@@ -38,6 +38,7 @@
38
38
  "node-fetch": "^2.6.0"
39
39
  },
40
40
  "devDependencies": {
41
+ "@agoric/cosmic-proto": "0.3.1-dev-dfc712f.0+dfc712f",
41
42
  "@endo/ses-ava": "^0.2.40",
42
43
  "@types/node-fetch": "^2.6.2",
43
44
  "ava": "^5.3.0",
@@ -58,5 +59,5 @@
58
59
  "timeout": "20m",
59
60
  "workerThreads": false
60
61
  },
61
- "gitHead": "648d42f4aedfcb521f5eaeea8dd10dd09e97b833"
62
+ "gitHead": "dfc712fe1fe243625d24b472acb72cad32c24b17"
62
63
  }
@@ -0,0 +1,2 @@
1
+ export function makeHttpClient(url: string, fetch: ((input: RequestInfo | URL, init?: RequestInit | undefined) => Promise<Response>) & ((input: RequestInfo | URL, init?: RequestInit | undefined) => Promise<Response>)): import('@cosmjs/tendermint-rpc').RpcClient;
2
+ //# sourceMappingURL=makeHttpClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"makeHttpClient.d.ts","sourceRoot":"","sources":["makeHttpClient.js"],"names":[],"mappings":"AA2BO,oCAJI,MAAM,iLAEJ,OAAO,wBAAwB,EAAE,SAAS,CA+BtD"}
@@ -0,0 +1,57 @@
1
+ // @ts-check
2
+
3
+ const { freeze } = Object;
4
+
5
+ const filterBadStatus = res => {
6
+ if (res.status >= 400) {
7
+ throw new Error(`Bad status on response: ${res.status}`);
8
+ }
9
+ return res;
10
+ };
11
+
12
+ /**
13
+ * Make an RpcClient using explicit access to the network.
14
+ *
15
+ * The RpcClient implementations included in cosmjs
16
+ * such as {@link https://cosmos.github.io/cosmjs/latest/tendermint-rpc/classes/HttpClient.html HttpClient}
17
+ * use ambient authority (fetch or axios) for network access.
18
+ *
19
+ * To facilitate cooperation without vulnerability,
20
+ * as well as unit testing, etc. this RpcClient maker takes
21
+ * network access as a parameter, following
22
+ * {@link https://github.com/Agoric/agoric-sdk/wiki/OCap-Discipline|OCap Discipline}.
23
+ *
24
+ * @param {string} url
25
+ * @param {typeof window.fetch} fetch
26
+ * @returns {import('@cosmjs/tendermint-rpc').RpcClient}
27
+ */
28
+ export const makeHttpClient = (url, fetch) => {
29
+ const headers = {}; // XXX needed?
30
+
31
+ // based on cosmjs 0.30.1:
32
+ // https://github.com/cosmos/cosmjs/blob/33271bc51cdc865cadb647a1b7ab55d873637f39/packages/tendermint-rpc/src/rpcclients/http.ts#L37
33
+ // https://github.com/cosmos/cosmjs/blob/33271bc51cdc865cadb647a1b7ab55d873637f39/packages/tendermint-rpc/src/rpcclients/httpclient.ts#L25
34
+ return freeze({
35
+ disconnect: () => {
36
+ // nothing to be done
37
+ },
38
+
39
+ /**
40
+ * @param {import('@cosmjs/json-rpc').JsonRpcRequest} request
41
+ */
42
+ execute: async request => {
43
+ const settings = {
44
+ method: 'POST',
45
+ body: request ? JSON.stringify(request) : undefined,
46
+ headers: {
47
+ // eslint-disable-next-line @typescript-eslint/naming-convention
48
+ 'Content-Type': 'application/json',
49
+ ...headers,
50
+ },
51
+ };
52
+ return fetch(url, settings)
53
+ .then(filterBadStatus)
54
+ .then(res => res.json());
55
+ },
56
+ });
57
+ };
@@ -0,0 +1,196 @@
1
+ const { stringify: jq } = JSON;
2
+
3
+ /**
4
+ * @file to regenerate
5
+ * 1. set RECORDING=true in test-interpose-net-access.js
6
+ * 2. run: yarn test test/test-test-interpose-net-access.js --update-snapshots
7
+ * 3. for each map in test-test-interpose-net-access.js.md, copy it and
8
+ * 4. replace all occurences of => with : and paste as args to Object.fromEntries()
9
+ * 5. change RECORDING back to false
10
+ */
11
+ export const web1 = new Map([
12
+ [
13
+ jq([
14
+ 'https://emerynet.rpc.agoric.net/',
15
+ {
16
+ method: 'POST',
17
+ body: jq({
18
+ id: 1208387614,
19
+ method: 'no-such-method',
20
+ params: [],
21
+ jsonrpc: '2.0',
22
+ }),
23
+ headers: { 'Content-Type': 'application/json' },
24
+ },
25
+ ]),
26
+ {
27
+ error: {
28
+ code: -32601,
29
+ message: 'Method not found',
30
+ },
31
+ id: 1208387614,
32
+ jsonrpc: '2.0',
33
+ },
34
+ ],
35
+ [
36
+ jq([
37
+ 'https://emerynet.rpc.agoric.net/',
38
+ {
39
+ method: 'POST',
40
+ body: jq({
41
+ jsonrpc: '2.0',
42
+ id: 797030719,
43
+ method: 'abci_query',
44
+ params: {
45
+ path: '/cosmos.bank.v1beta1.Query/Balance',
46
+ data: '0a2d61676f726963313430646d6b727a326534326572676a6a37677976656a687a6d6a7a7572767165713832616e67120475697374',
47
+ prove: false,
48
+ },
49
+ }),
50
+ headers: { 'Content-Type': 'application/json' },
51
+ },
52
+ ]),
53
+ {
54
+ id: 797030719,
55
+ jsonrpc: '2.0',
56
+ result: {
57
+ response: {
58
+ code: 0,
59
+ codespace: '',
60
+ height: '123985',
61
+ index: '0',
62
+ info: '',
63
+ key: null,
64
+ log: '',
65
+ proofOps: null,
66
+ value: 'ChAKBHVpc3QSCDI1MDUwMDAw',
67
+ },
68
+ },
69
+ },
70
+ ],
71
+ ]);
72
+
73
+ export const web2 = new Map([
74
+ [
75
+ jq([
76
+ 'https://emerynet.rpc.agoric.net/',
77
+ {
78
+ method: 'POST',
79
+ body: jq({
80
+ jsonrpc: '2.0',
81
+ id: 1757612624,
82
+ method: 'abci_query',
83
+ params: {
84
+ path: '/agoric.vstorage.Query/Children',
85
+ data: '',
86
+ prove: false,
87
+ },
88
+ }),
89
+ headers: { 'Content-Type': 'application/json' },
90
+ },
91
+ ]),
92
+ {
93
+ id: 1757612624,
94
+ jsonrpc: '2.0',
95
+ result: {
96
+ response: {
97
+ code: 0,
98
+ codespace: '',
99
+ height: '123985',
100
+ index: '0',
101
+ info: '',
102
+ key: null,
103
+ log: '',
104
+ proofOps: null,
105
+ value:
106
+ 'CgxhY3Rpdml0eWhhc2gKCmJlYW5zT3dpbmcKBmVncmVzcwoTaGlnaFByaW9yaXR5U2VuZGVycwoJcHVibGlzaGVkCgpzd2luZ1N0b3Jl',
107
+ },
108
+ },
109
+ },
110
+ ],
111
+ ]);
112
+
113
+ /**
114
+ * @param {string} str
115
+ * ack: https://stackoverflow.com/a/7616484
116
+ */
117
+ const hashCode = str => {
118
+ let hash = 0;
119
+ let i;
120
+ let chr;
121
+ if (str.length === 0) return hash;
122
+ for (i = 0; i < str.length; i += 1) {
123
+ chr = str.charCodeAt(i);
124
+ // eslint-disable-next-line no-bitwise
125
+ hash = (hash << 5) - hash + chr;
126
+ // eslint-disable-next-line no-bitwise
127
+ hash |= 0; // Convert to 32bit integer
128
+ }
129
+ return hash;
130
+ };
131
+
132
+ /**
133
+ * Normalize JSON RPC request ID
134
+ *
135
+ * tendermint-rpc generates ids using ambient access to Math.random()
136
+ * So we normalize them to a hash of the rest of the JSON.
137
+ *
138
+ * Earlier, we tried a sequence number, but it was non-deterministic
139
+ * with multiple interleaved requests.
140
+ *
141
+ * @param {string} argsKey
142
+ */
143
+ const normalizeID = argsKey => {
144
+ // arbitrary string unlikely to occur in a request. from `pwgen 16 -1`
145
+ const placeholder = 'Ajaz1chei7ohnguv';
146
+
147
+ const noid = argsKey.replace(/\\"id\\":\d+/, `\\"id\\":${placeholder}`);
148
+ const id = Math.abs(hashCode(noid));
149
+ return noid.replace(placeholder, `${id}`);
150
+ };
151
+
152
+ /**
153
+ * Wrap `fetch` to capture JSON RPC IO traffic.
154
+ *
155
+ * @param {typeof window.fetch} fetch
156
+ * returns wraped fetch along with a .web map for use with {@link replayIO}
157
+ */
158
+ export const captureIO = fetch => {
159
+ const web = new Map();
160
+ /** @type {typeof window.fetch} */
161
+ // @ts-expect-error mock
162
+ const f = async (...args) => {
163
+ const key = normalizeID(JSON.stringify(args));
164
+ const resp = await fetch(...args);
165
+ return {
166
+ json: async () => {
167
+ const data = await resp.json();
168
+ web.set(key, data);
169
+ return data;
170
+ },
171
+ };
172
+ };
173
+ return { fetch: f, web };
174
+ };
175
+
176
+ /**
177
+ * Replay captured JSON RPC IO.
178
+ *
179
+ * @param {Map<string, any>} web map from
180
+ * JSON-stringified fetch args to fetched JSON data.
181
+ */
182
+ export const replayIO = web => {
183
+ /** @type {typeof window.fetch} */
184
+ // @ts-expect-error mock
185
+ const f = async (...args) => {
186
+ const key = normalizeID(JSON.stringify(args));
187
+ const data = web.get(key);
188
+ if (!data) {
189
+ throw Error(`no data for ${key}`);
190
+ }
191
+ return {
192
+ json: async () => data,
193
+ };
194
+ };
195
+ return f;
196
+ };
@@ -0,0 +1,97 @@
1
+ // @ts-check
2
+ /* global globalThis */
3
+ import anyTest from 'ava';
4
+ import {
5
+ createProtobufRpcClient,
6
+ QueryClient,
7
+ setupBankExtension,
8
+ } from '@cosmjs/stargate';
9
+ import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
10
+ import { QueryClientImpl } from '@agoric/cosmic-proto/vstorage/query.js';
11
+
12
+ import { makeHttpClient } from '../src/makeHttpClient.js';
13
+ import { captureIO, replayIO, web1, web2 } from './net-access-fixture.js';
14
+
15
+ /** @type {import('ava').TestFn<Awaited<ReturnType<typeof makeTestContext>>>} */
16
+ const test = /** @type {any} */ (anyTest);
17
+
18
+ const RECORDING = false;
19
+
20
+ const makeTestContext = async () => {
21
+ return { fetch: globalThis.fetch };
22
+ };
23
+
24
+ test.before(async t => {
25
+ t.context = await makeTestContext();
26
+ });
27
+
28
+ const scenario1 = {
29
+ endpoint: 'https://emerynet.rpc.agoric.net/',
30
+ request: {
31
+ id: 1,
32
+ method: 'no-such-method',
33
+ params: [],
34
+ },
35
+ gov2: {
36
+ addr: 'agoric140dmkrz2e42ergjj7gyvejhzmjzurvqeq82ang',
37
+ balance: { amount: '25050000', denom: 'uist' },
38
+ },
39
+ };
40
+
41
+ test('interpose net access', async t => {
42
+ const fetchMock = replayIO(web1);
43
+ const rpcClient = makeHttpClient(scenario1.endpoint, fetchMock);
44
+
45
+ t.log('raw JSON RPC');
46
+ const res = await rpcClient.execute({
47
+ ...scenario1.request,
48
+ jsonrpc: '2.0',
49
+ });
50
+ t.like(res, { error: { message: 'Method not found' } });
51
+
52
+ t.log('Cosmos SDK RPC: balance query');
53
+ const tmClient = await Tendermint34Client.create(rpcClient);
54
+ const qClient = new QueryClient(tmClient);
55
+ const ext = setupBankExtension(qClient);
56
+ const actual = await ext.bank.balance(
57
+ scenario1.gov2.addr,
58
+ scenario1.gov2.balance.denom,
59
+ );
60
+
61
+ t.deepEqual(actual, scenario1.gov2.balance);
62
+ });
63
+
64
+ const scenario2 = {
65
+ endpoint: 'https://emerynet.rpc.agoric.net/',
66
+ children: [
67
+ 'activityhash',
68
+ 'beansOwing',
69
+ 'egress',
70
+ 'highPrioritySenders',
71
+ 'published',
72
+ 'swingStore',
73
+ ],
74
+ };
75
+
76
+ test(`vstorage query: Children (RECORDING: ${RECORDING})`, async t => {
77
+ const { context: io } = t;
78
+
79
+ const { fetch: fetchMock, web } = io.recording
80
+ ? captureIO(io.fetch)
81
+ : { fetch: replayIO(web2), web: new Map() };
82
+ const rpcClient = makeHttpClient(scenario2.endpoint, fetchMock);
83
+
84
+ const tmClient = await Tendermint34Client.create(rpcClient);
85
+ const qClient = new QueryClient(tmClient);
86
+ const rpc = createProtobufRpcClient(qClient);
87
+ const queryService = new QueryClientImpl(rpc);
88
+
89
+ const children = await queryService.Children({ path: '' });
90
+ if (io.recording) {
91
+ t.snapshot(web);
92
+ }
93
+ t.deepEqual(children, {
94
+ children: scenario2.children,
95
+ pagination: undefined,
96
+ });
97
+ });