@agoric/cosmic-swingset 0.40.0 → 0.40.1-dev-800c2bb.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/Makefile +63 -34
- package/README-bridge.md +0 -3
- package/README.md +0 -2
- package/bin/ag-nchainz +1 -1
- package/bootstrap.json +2 -0
- package/calc-rpcport.js +2 -1
- package/check-validator.cjs +2 -0
- package/economy-template.json +67 -5
- package/jsconfig.json +2 -14
- package/package.json +20 -27
- package/rosetta.json +54 -0
- package/src/chain-main.js +422 -213
- package/src/entrypoint.js +3 -5
- package/src/export-kernel-db.js +430 -0
- package/src/export-storage.js +91 -0
- package/src/helpers/bufferedStorage.js +194 -0
- package/src/{is-entrypoint.js → helpers/is-entrypoint.js} +2 -0
- package/src/{json-stable-stringify.js → helpers/json-stable-stringify.js} +1 -1
- package/src/helpers/make-queue.js +134 -0
- package/src/helpers/process-value.js +133 -0
- package/src/import-kernel-db.js +207 -0
- package/src/kernel-stats.js +88 -18
- package/src/launch-chain.js +265 -206
- package/src/params.js +20 -21
- package/src/sim-chain.js +58 -54
- package/src/sim-params.js +4 -3
- package/transfer.ros +161 -0
- package/src/action-types.js +0 -13
- package/src/make-queue.js +0 -86
- package/src/validate-inbound.js +0 -109
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { assert, Fail } from '@agoric/assert';
|
|
4
|
+
|
|
5
|
+
// XXX Do these "StorageAPI" functions belong in their own package?
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @template {unknown} [T=unknown]
|
|
9
|
+
* @typedef {{
|
|
10
|
+
* has: (key: string) => boolean,
|
|
11
|
+
* get: (key: string) => T | undefined,
|
|
12
|
+
* getNextKey: (previousKey: string) => string | undefined,
|
|
13
|
+
* set: (key: string, value: T ) => void,
|
|
14
|
+
* delete: (key: string) => void,
|
|
15
|
+
* }} KVStore
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Assert function to ensure that an object implements the StorageAPI
|
|
20
|
+
* interface: methods { has, getNextKey, get, set, delete }
|
|
21
|
+
* (cf. packages/SwingSet/docs/state.md#transactions).
|
|
22
|
+
*
|
|
23
|
+
* @param {*} kvStore The object to be tested
|
|
24
|
+
*
|
|
25
|
+
* @throws {Error} if, upon inspection, the parameter does not satisfy the above
|
|
26
|
+
* criteria.
|
|
27
|
+
*
|
|
28
|
+
* @returns {void}
|
|
29
|
+
*/
|
|
30
|
+
export function insistStorageAPI(kvStore) {
|
|
31
|
+
for (const n of ['has', 'getNextKey', 'get', 'set', 'delete']) {
|
|
32
|
+
n in kvStore || Fail`kvStore.${n} is missing, cannot use`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a StorageAPI object that buffers writes to a wrapped StorageAPI object
|
|
38
|
+
* until told to commit (or abort) them.
|
|
39
|
+
*
|
|
40
|
+
* @template {unknown} [T=unknown]
|
|
41
|
+
* @param {KVStore<T>} kvStore The StorageAPI object to wrap
|
|
42
|
+
* @param {{
|
|
43
|
+
* onGet?: (key: string, value: T) => void, // a callback invoked after getting a value from kvStore
|
|
44
|
+
* onPendingSet?: (key: string, value: T) => void, // a callback invoked after a new uncommitted value is set
|
|
45
|
+
* onPendingDelete?: (key: string) => void, // a callback invoked after a new uncommitted delete
|
|
46
|
+
* onCommit?: () => void, // a callback invoked after pending operations have been committed
|
|
47
|
+
* onAbort?: () => void, // a callback invoked after pending operations have been aborted
|
|
48
|
+
* }} listeners Optional callbacks to be invoked when respective events occur
|
|
49
|
+
*
|
|
50
|
+
* @returns {{kvStore: KVStore<T>, commit: () => void, abort: () => void}}
|
|
51
|
+
*/
|
|
52
|
+
export function makeBufferedStorage(kvStore, listeners = {}) {
|
|
53
|
+
insistStorageAPI(kvStore);
|
|
54
|
+
|
|
55
|
+
const { onGet, onPendingSet, onPendingDelete, onCommit, onAbort } = listeners;
|
|
56
|
+
|
|
57
|
+
// To avoid confusion, additions and deletions are prevented from sharing
|
|
58
|
+
// the same key at any given time.
|
|
59
|
+
const additions = new Map();
|
|
60
|
+
const deletions = new Set();
|
|
61
|
+
|
|
62
|
+
const buffered = {
|
|
63
|
+
has(key) {
|
|
64
|
+
assert.typeof(key, 'string');
|
|
65
|
+
if (additions.has(key)) return true;
|
|
66
|
+
if (deletions.has(key)) return false;
|
|
67
|
+
return kvStore.has(key);
|
|
68
|
+
},
|
|
69
|
+
get(key) {
|
|
70
|
+
assert.typeof(key, 'string');
|
|
71
|
+
if (additions.has(key)) return additions.get(key);
|
|
72
|
+
if (deletions.has(key)) return undefined;
|
|
73
|
+
const value = kvStore.get(key);
|
|
74
|
+
// @ts-expect-error value may be undefined
|
|
75
|
+
if (onGet !== undefined) onGet(key, value);
|
|
76
|
+
return value;
|
|
77
|
+
},
|
|
78
|
+
set(key, value) {
|
|
79
|
+
assert.typeof(key, 'string');
|
|
80
|
+
additions.set(key, value);
|
|
81
|
+
deletions.delete(key);
|
|
82
|
+
if (onPendingSet !== undefined) onPendingSet(key, value);
|
|
83
|
+
},
|
|
84
|
+
delete(key) {
|
|
85
|
+
assert.typeof(key, 'string');
|
|
86
|
+
additions.delete(key);
|
|
87
|
+
deletions.add(key);
|
|
88
|
+
if (onPendingDelete !== undefined) onPendingDelete(key);
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {string} previousKey
|
|
93
|
+
*/
|
|
94
|
+
getNextKey(previousKey) {
|
|
95
|
+
assert.typeof(previousKey, 'string');
|
|
96
|
+
throw Error('not implemented');
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
function commit() {
|
|
100
|
+
for (const [key, value] of additions) {
|
|
101
|
+
kvStore.set(key, value);
|
|
102
|
+
}
|
|
103
|
+
for (const key of deletions) {
|
|
104
|
+
kvStore.delete(key);
|
|
105
|
+
}
|
|
106
|
+
additions.clear();
|
|
107
|
+
deletions.clear();
|
|
108
|
+
if (onCommit !== undefined) onCommit();
|
|
109
|
+
}
|
|
110
|
+
function abort() {
|
|
111
|
+
additions.clear();
|
|
112
|
+
deletions.clear();
|
|
113
|
+
if (onAbort !== undefined) onAbort();
|
|
114
|
+
}
|
|
115
|
+
return { kvStore: buffered, commit, abort };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @template {unknown} [T=unknown]
|
|
120
|
+
* @param {KVStore<T>} kvStore
|
|
121
|
+
*/
|
|
122
|
+
export const makeReadCachingStorage = kvStore => {
|
|
123
|
+
// In addition to the wrapping write buffer, keep a simple cache of
|
|
124
|
+
// read values for has and get.
|
|
125
|
+
let cache;
|
|
126
|
+
function resetCache() {
|
|
127
|
+
cache = new Map();
|
|
128
|
+
}
|
|
129
|
+
resetCache();
|
|
130
|
+
|
|
131
|
+
const deleted = Symbol('deleted');
|
|
132
|
+
const undef = Symbol('undefined');
|
|
133
|
+
|
|
134
|
+
/** @type {KVStore<T>} */
|
|
135
|
+
const storage = harden({
|
|
136
|
+
has(key) {
|
|
137
|
+
const value = cache.get(key);
|
|
138
|
+
if (value !== undefined) {
|
|
139
|
+
return value !== deleted;
|
|
140
|
+
} else {
|
|
141
|
+
const ret = kvStore.has(key);
|
|
142
|
+
if (!ret) {
|
|
143
|
+
cache.set(key, deleted);
|
|
144
|
+
}
|
|
145
|
+
return ret;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
get(key) {
|
|
149
|
+
let value = cache.get(key);
|
|
150
|
+
if (value !== undefined) {
|
|
151
|
+
return value === deleted || value === undef ? undefined : value;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Fetch the value and cache it until the next commit or abort.
|
|
155
|
+
value = kvStore.get(key);
|
|
156
|
+
cache.set(key, value === undefined ? undef : value);
|
|
157
|
+
return value;
|
|
158
|
+
},
|
|
159
|
+
set(key, value) {
|
|
160
|
+
// Set the value and cache it until the next commit or abort (which is
|
|
161
|
+
// expected immediately, since the buffered wrapper only calls set
|
|
162
|
+
// *during* a commit).
|
|
163
|
+
// `undefined` is a valid value technically different than deletion,
|
|
164
|
+
// depending on how the underlying store does its serialization
|
|
165
|
+
cache.set(key, value === undefined ? undef : value);
|
|
166
|
+
kvStore.set(key, value);
|
|
167
|
+
},
|
|
168
|
+
delete(key) {
|
|
169
|
+
// Delete the value and cache the deletion until next commit or abort.
|
|
170
|
+
// Deletion results in `undefined` on `get`, but `false` on `has`
|
|
171
|
+
cache.set(key, deleted);
|
|
172
|
+
kvStore.delete(key);
|
|
173
|
+
},
|
|
174
|
+
getNextKey(_previousKey) {
|
|
175
|
+
throw Error('not implemented');
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
const {
|
|
179
|
+
kvStore: buffered,
|
|
180
|
+
commit,
|
|
181
|
+
abort,
|
|
182
|
+
} = makeBufferedStorage(storage, {
|
|
183
|
+
// Enqueue a write of any retrieved value, to handle callers like mailbox.js
|
|
184
|
+
// that expect local mutations to be automatically written back.
|
|
185
|
+
onGet(key, value) {
|
|
186
|
+
buffered.set(key, value);
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
// Reset the read cache upon commit or abort.
|
|
190
|
+
onCommit: resetCache,
|
|
191
|
+
onAbort: resetCache,
|
|
192
|
+
});
|
|
193
|
+
return harden({ ...buffered, commit, abort });
|
|
194
|
+
};
|
|
@@ -74,7 +74,7 @@ export default function stableStringify(obj, opts) {
|
|
|
74
74
|
}
|
|
75
75
|
if (seen.indexOf(node) !== -1) {
|
|
76
76
|
if (cycles) return JSON.stringify('__cycle__');
|
|
77
|
-
throw
|
|
77
|
+
throw TypeError('Converting circular structure to JSON');
|
|
78
78
|
} else seen.push(node);
|
|
79
79
|
|
|
80
80
|
const keys = objectKeys(node).sort(cmp && cmp(node));
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { Fail } from '@agoric/assert';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} QueueStorage
|
|
7
|
+
* @property {() => void} commit
|
|
8
|
+
* @property {() => void} abort
|
|
9
|
+
* @property {(key: string) => string | undefined} get
|
|
10
|
+
* @property {(key: string, value: string) => void} set
|
|
11
|
+
* @property {(key: string) => void} delete
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {{[idx: number]: string | undefined, head?: string, tail?: string}} QueueStorageDump
|
|
16
|
+
* @param {QueueStorageDump} [init]
|
|
17
|
+
* @returns {{storage: QueueStorage; dump: () => QueueStorageDump}}
|
|
18
|
+
*/
|
|
19
|
+
export const makeQueueStorageMock = init => {
|
|
20
|
+
const storage = new Map(init && Object.entries(init));
|
|
21
|
+
return harden({
|
|
22
|
+
storage: {
|
|
23
|
+
get(key) {
|
|
24
|
+
return storage.get(key);
|
|
25
|
+
},
|
|
26
|
+
set(key, value) {
|
|
27
|
+
typeof value === 'string' || Fail`invalid value type ${value}`;
|
|
28
|
+
storage.set(key, value);
|
|
29
|
+
},
|
|
30
|
+
delete(key) {
|
|
31
|
+
storage.delete(key);
|
|
32
|
+
},
|
|
33
|
+
commit() {},
|
|
34
|
+
abort() {},
|
|
35
|
+
},
|
|
36
|
+
dump: () => harden(Object.fromEntries(storage.entries())),
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a queue backed by some sort of scoped storage.
|
|
42
|
+
*
|
|
43
|
+
* The queue writes the following bare keys, and expect any prefixing/scoping
|
|
44
|
+
* to be handled by the storage:
|
|
45
|
+
* - `head`: the index of the first entry of the queue.
|
|
46
|
+
* - `tail`: the index *past* the last entry in the queue.
|
|
47
|
+
* - `<index>`: the contents of the queue at the given index.
|
|
48
|
+
*
|
|
49
|
+
* For the cosmos inbound queues (`actionQueue` or `highPriorityQueue`), the
|
|
50
|
+
* golang side will push into the queue, updating the index stored at key
|
|
51
|
+
* `${queuePath}.tail` and setting data for key `${queuePath}.${index}`.
|
|
52
|
+
* The JS side will shift the queue, updating the index at key
|
|
53
|
+
* `${queuePath}.head` and reading and deleting `${queuePath}.${index}`.
|
|
54
|
+
*
|
|
55
|
+
* Parallel access is not supported, only a single outstanding operation at a
|
|
56
|
+
* time.
|
|
57
|
+
*
|
|
58
|
+
* @template {unknown} [T=unknown]
|
|
59
|
+
* @param {QueueStorage} storage a scoped queue storage
|
|
60
|
+
*/
|
|
61
|
+
export const makeQueue = storage => {
|
|
62
|
+
const getHead = () => BigInt(storage.get('head') || 0);
|
|
63
|
+
const getTail = () => BigInt(storage.get('tail') || 0);
|
|
64
|
+
|
|
65
|
+
/** @type {IterableIterator<T> | null} */
|
|
66
|
+
let currentIterator = null;
|
|
67
|
+
const queue = {
|
|
68
|
+
size: () => {
|
|
69
|
+
return Number(getTail() - getHead());
|
|
70
|
+
},
|
|
71
|
+
/** @param {T} obj */
|
|
72
|
+
push: obj => {
|
|
73
|
+
const tail = getTail();
|
|
74
|
+
storage.set('tail', String(tail + 1n));
|
|
75
|
+
storage.set(`${tail}`, JSON.stringify(obj));
|
|
76
|
+
storage.commit();
|
|
77
|
+
currentIterator = null;
|
|
78
|
+
},
|
|
79
|
+
/** @returns {IterableIterator<T>} */
|
|
80
|
+
consumeAll: () => {
|
|
81
|
+
let head = getHead();
|
|
82
|
+
const tail = getTail();
|
|
83
|
+
let done = !(head < tail);
|
|
84
|
+
const iterator = {
|
|
85
|
+
[Symbol.iterator]: () => iterator,
|
|
86
|
+
next: () => {
|
|
87
|
+
currentIterator === iterator || Fail`invalid iterator`;
|
|
88
|
+
if (!done) {
|
|
89
|
+
if (head < tail) {
|
|
90
|
+
// Still within the queue.
|
|
91
|
+
const headKey = `${head}`;
|
|
92
|
+
const value = JSON.parse(
|
|
93
|
+
/** @type {string} */ (storage.get(headKey)),
|
|
94
|
+
);
|
|
95
|
+
storage.delete(headKey);
|
|
96
|
+
head += 1n;
|
|
97
|
+
return { value, done };
|
|
98
|
+
}
|
|
99
|
+
// Reached the end, so clean up our indices.
|
|
100
|
+
storage.delete('head');
|
|
101
|
+
storage.delete('tail');
|
|
102
|
+
storage.commit();
|
|
103
|
+
done = true;
|
|
104
|
+
}
|
|
105
|
+
return { value: undefined, done };
|
|
106
|
+
},
|
|
107
|
+
return: () => {
|
|
108
|
+
currentIterator === iterator || Fail`invalid iterator`;
|
|
109
|
+
if (!done) {
|
|
110
|
+
// We're done consuming, so save our state.
|
|
111
|
+
storage.set('head', String(head));
|
|
112
|
+
storage.commit();
|
|
113
|
+
done = true;
|
|
114
|
+
}
|
|
115
|
+
return { value: undefined, done };
|
|
116
|
+
},
|
|
117
|
+
throw: err => {
|
|
118
|
+
currentIterator === iterator || Fail`invalid iterator`;
|
|
119
|
+
if (!done) {
|
|
120
|
+
// Don't change our state.
|
|
121
|
+
storage.abort();
|
|
122
|
+
done = true;
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
return { value: undefined, done };
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
currentIterator = iterator;
|
|
129
|
+
return iterator;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
return queue;
|
|
133
|
+
};
|
|
134
|
+
harden(makeQueue);
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { resolve as pathResolve } from 'path';
|
|
4
|
+
|
|
5
|
+
export const makeProcessValue = ({ env, args }) => {
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} flagName
|
|
8
|
+
* @param {string | undefined} [deflt]
|
|
9
|
+
* @returns {string | undefined}
|
|
10
|
+
*/
|
|
11
|
+
const getFlag = (flagName, deflt) => {
|
|
12
|
+
let flagValue = deflt;
|
|
13
|
+
const envValue =
|
|
14
|
+
env[`AG_CHAIN_COSMOS_${flagName.toUpperCase().replace(/-/g, '_')}`];
|
|
15
|
+
if (envValue !== undefined) {
|
|
16
|
+
flagValue = envValue;
|
|
17
|
+
}
|
|
18
|
+
const flag = `--${flagName}`;
|
|
19
|
+
const flagEquals = `--${flagName}=`;
|
|
20
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
21
|
+
const arg = args[i];
|
|
22
|
+
if (arg === flag) {
|
|
23
|
+
if (args.length > i + 1 && !args[i + 1].startsWith('--')) {
|
|
24
|
+
i += 1;
|
|
25
|
+
flagValue = args[i];
|
|
26
|
+
} else {
|
|
27
|
+
flagValue = '';
|
|
28
|
+
}
|
|
29
|
+
} else if (arg.startsWith(flagEquals)) {
|
|
30
|
+
flagValue = arg.substr(flagEquals.length);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return flagValue;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {object} options
|
|
38
|
+
* @param {string} [options.envName]
|
|
39
|
+
* @param {string} [options.flagName]
|
|
40
|
+
* @param {boolean} [options.emptyFlagIsTrue]
|
|
41
|
+
* @returns {boolean | undefined}
|
|
42
|
+
*/
|
|
43
|
+
const getBoolean = ({ envName, flagName, emptyFlagIsTrue = true }) => {
|
|
44
|
+
let option;
|
|
45
|
+
if (flagName) {
|
|
46
|
+
option = getFlag(flagName);
|
|
47
|
+
} else if (envName) {
|
|
48
|
+
option = env[envName];
|
|
49
|
+
} else {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
switch (option) {
|
|
54
|
+
case '0':
|
|
55
|
+
case 'false':
|
|
56
|
+
case false:
|
|
57
|
+
return false;
|
|
58
|
+
case '1':
|
|
59
|
+
case 'true':
|
|
60
|
+
case true:
|
|
61
|
+
return true;
|
|
62
|
+
default:
|
|
63
|
+
if (option === '' && flagName && emptyFlagIsTrue) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (option === undefined && envName && flagName) {
|
|
67
|
+
return getBoolean({ envName, emptyFlagIsTrue });
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {object} options
|
|
75
|
+
* @param {string} [options.envName]
|
|
76
|
+
* @param {string} [options.flagName]
|
|
77
|
+
* @returns {number | undefined}
|
|
78
|
+
*/
|
|
79
|
+
const getInteger = ({ envName, flagName }) => {
|
|
80
|
+
let option;
|
|
81
|
+
if (flagName) {
|
|
82
|
+
option = getFlag(flagName);
|
|
83
|
+
} else if (envName) {
|
|
84
|
+
option = env[envName];
|
|
85
|
+
} else {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (option) {
|
|
90
|
+
const value = Number.parseInt(option, 10);
|
|
91
|
+
return String(value) === option ? value : undefined;
|
|
92
|
+
} else if (option === undefined && flagName && envName) {
|
|
93
|
+
return getInteger({ envName });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return undefined;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {object} options
|
|
101
|
+
* @param {string} [options.envName]
|
|
102
|
+
* @param {string} [options.flagName]
|
|
103
|
+
* @param {string} [options.trueValue]
|
|
104
|
+
* @returns {string | undefined}
|
|
105
|
+
*/
|
|
106
|
+
const getPath = ({ envName, flagName, trueValue }) => {
|
|
107
|
+
if (trueValue !== undefined) {
|
|
108
|
+
const boolValue = getBoolean({ envName, flagName });
|
|
109
|
+
if (boolValue !== undefined) {
|
|
110
|
+
return boolValue ? trueValue : undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let option;
|
|
115
|
+
if (flagName) {
|
|
116
|
+
option = getFlag(flagName);
|
|
117
|
+
} else if (envName) {
|
|
118
|
+
option = env[envName];
|
|
119
|
+
} else {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (option) {
|
|
124
|
+
return pathResolve(option);
|
|
125
|
+
} else if (trueValue && envName && flagName) {
|
|
126
|
+
return getPath({ envName, trueValue });
|
|
127
|
+
} else {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return harden({ getFlag, getBoolean, getInteger, getPath });
|
|
133
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
// @ts-check
|
|
3
|
+
// @jessie-check
|
|
4
|
+
|
|
5
|
+
import '@endo/init/unsafe-fast.js';
|
|
6
|
+
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import process from 'process';
|
|
9
|
+
import { Transform } from 'stream';
|
|
10
|
+
import fsPower from 'fs';
|
|
11
|
+
import fsPromisesPower from 'fs/promises';
|
|
12
|
+
import pathPower from 'path';
|
|
13
|
+
|
|
14
|
+
import BufferLineTransform from '@agoric/internal/src/node/buffer-line-transform.js';
|
|
15
|
+
import { Fail } from '@agoric/assert';
|
|
16
|
+
import { importSwingStore } from '@agoric/swing-store';
|
|
17
|
+
|
|
18
|
+
import { isEntrypoint } from './helpers/is-entrypoint.js';
|
|
19
|
+
import { makeProcessValue } from './helpers/process-value.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {object} StateSyncImporterOptions
|
|
23
|
+
* @property {string} stateDir the directory containing the SwingStore to export
|
|
24
|
+
* @property {string} exportDir the directory where to place the exported artifacts and manifest
|
|
25
|
+
* @property {number} [blockHeight] block height to check for
|
|
26
|
+
* @property {boolean} [includeHistorical] whether to include historical artifacts in the export
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {StateSyncImporterOptions} options
|
|
31
|
+
* @param {object} powers
|
|
32
|
+
* @param {Pick<import('fs/promises'), 'readFile'> & Pick<import('fs'), 'createReadStream'>} powers.fs
|
|
33
|
+
* @param {import('path')['resolve']} powers.pathResolve
|
|
34
|
+
* @param {typeof import('@agoric/swing-store')['importSwingStore']} [powers.importSwingStore]
|
|
35
|
+
* @param {null | ((...args: any[]) => void)} [powers.log]
|
|
36
|
+
* @returns {Promise<void>}
|
|
37
|
+
*/
|
|
38
|
+
export const performStateSyncImport = async (
|
|
39
|
+
{ stateDir, exportDir, blockHeight, includeHistorical },
|
|
40
|
+
{
|
|
41
|
+
fs: { createReadStream, readFile },
|
|
42
|
+
pathResolve,
|
|
43
|
+
importSwingStore: importDB = importSwingStore,
|
|
44
|
+
log = console.log,
|
|
45
|
+
},
|
|
46
|
+
) => {
|
|
47
|
+
/** @param {string} allegedRelativeFilename */
|
|
48
|
+
const safeExportFileResolve = allegedRelativeFilename => {
|
|
49
|
+
const resolvedPath = pathResolve(exportDir, allegedRelativeFilename);
|
|
50
|
+
resolvedPath.startsWith(exportDir) ||
|
|
51
|
+
Fail`Exported file ${allegedRelativeFilename} must be in export dir ${exportDir}`;
|
|
52
|
+
return resolvedPath;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const manifestPath = safeExportFileResolve('export-manifest.json');
|
|
56
|
+
/** @type {Readonly<import('./export-kernel-db.js').StateSyncManifest>} */
|
|
57
|
+
const manifest = await readFile(manifestPath, { encoding: 'utf-8' }).then(
|
|
58
|
+
data => JSON.parse(data),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (blockHeight !== undefined && manifest.blockHeight !== blockHeight) {
|
|
62
|
+
Fail`State-sync manifest for unexpected block height ${manifest.blockHeight} (expected ${blockHeight})`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!manifest.data) {
|
|
66
|
+
throw Fail`State-sync manifest missing export data`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!manifest.artifacts) {
|
|
70
|
+
throw Fail`State-sync manifest missing required artifacts`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const artifacts = harden(Object.fromEntries(manifest.artifacts));
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
includeHistorical &&
|
|
77
|
+
manifest.mode !== 'archival' &&
|
|
78
|
+
manifest.mode !== 'debug'
|
|
79
|
+
) {
|
|
80
|
+
throw Fail`State-sync manifest missing historical artifacts`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Represent the data in `exportDir` as a SwingSetExporter object.
|
|
84
|
+
/** @type {import('@agoric/swing-store').SwingStoreExporter} */
|
|
85
|
+
const exporter = harden({
|
|
86
|
+
async *getExportData() {
|
|
87
|
+
log?.('importing export data');
|
|
88
|
+
const exportData = createReadStream(
|
|
89
|
+
safeExportFileResolve(/** @type {string} */ (manifest.data)),
|
|
90
|
+
);
|
|
91
|
+
const exportDataEntries = exportData
|
|
92
|
+
.pipe(new BufferLineTransform())
|
|
93
|
+
.setEncoding('utf8')
|
|
94
|
+
.pipe(
|
|
95
|
+
new Transform({
|
|
96
|
+
objectMode: true,
|
|
97
|
+
transform(data, encoding, callback) {
|
|
98
|
+
try {
|
|
99
|
+
callback(null, JSON.parse(data));
|
|
100
|
+
} catch (error) {
|
|
101
|
+
callback(error);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
yield* exportDataEntries;
|
|
107
|
+
},
|
|
108
|
+
async *getArtifact(name) {
|
|
109
|
+
log?.(`importing artifact ${name}`);
|
|
110
|
+
const fileName = artifacts[name];
|
|
111
|
+
if (!fileName) {
|
|
112
|
+
Fail`invalid artifact ${name}`;
|
|
113
|
+
}
|
|
114
|
+
const stream = createReadStream(safeExportFileResolve(fileName));
|
|
115
|
+
yield* stream;
|
|
116
|
+
},
|
|
117
|
+
async *getArtifactNames() {
|
|
118
|
+
yield* Object.keys(artifacts);
|
|
119
|
+
},
|
|
120
|
+
async close() {
|
|
121
|
+
// TODO
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const swingstore = await importDB(exporter, stateDir, { includeHistorical });
|
|
126
|
+
|
|
127
|
+
const { hostStorage } = swingstore;
|
|
128
|
+
|
|
129
|
+
hostStorage.kvStore.set('host.height', String(manifest.blockHeight));
|
|
130
|
+
await hostStorage.commit();
|
|
131
|
+
await hostStorage.close();
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {string[]} args
|
|
136
|
+
* @param {object} powers
|
|
137
|
+
* @param {Partial<Record<string, string>>} powers.env
|
|
138
|
+
* @param {string} powers.homedir
|
|
139
|
+
* @param {Console} powers.console
|
|
140
|
+
* @param {Pick<import('fs/promises'), 'readFile' | 'stat'> & Pick<import('fs'), 'createReadStream'>} powers.fs
|
|
141
|
+
* @param {import('path')['resolve']} powers.pathResolve
|
|
142
|
+
*/
|
|
143
|
+
export const main = async (
|
|
144
|
+
args,
|
|
145
|
+
{ env, homedir, console, fs, pathResolve },
|
|
146
|
+
) => {
|
|
147
|
+
const processValue = makeProcessValue({ env, args });
|
|
148
|
+
|
|
149
|
+
const stateDir =
|
|
150
|
+
processValue.getFlag('state-dir') ||
|
|
151
|
+
// We try to find the actual cosmos state directory (default=~/.ag-chain-cosmos)
|
|
152
|
+
`${processValue.getFlag(
|
|
153
|
+
'home',
|
|
154
|
+
`${homedir}/.ag-chain-cosmos`,
|
|
155
|
+
)}/data/agoric`;
|
|
156
|
+
|
|
157
|
+
const stateDirStat = await fs.stat(stateDir);
|
|
158
|
+
if (!stateDirStat.isDirectory()) {
|
|
159
|
+
throw Error('state-dir must be an exiting directory');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const exportDir = pathResolve(
|
|
163
|
+
/** @type {string} */ (processValue.getFlag('export-dir', '.')),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const includeHistorical = processValue.getBoolean({
|
|
167
|
+
flagName: 'include-historical',
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const checkBlockHeight = processValue.getInteger({
|
|
171
|
+
flagName: 'check-block-height',
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const verbose = processValue.getBoolean({
|
|
175
|
+
flagName: 'verbose',
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
await performStateSyncImport(
|
|
179
|
+
{
|
|
180
|
+
stateDir,
|
|
181
|
+
exportDir,
|
|
182
|
+
blockHeight: checkBlockHeight,
|
|
183
|
+
includeHistorical,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
fs,
|
|
187
|
+
pathResolve,
|
|
188
|
+
log: verbose ? console.log : undefined,
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
if (isEntrypoint(import.meta.url)) {
|
|
194
|
+
main(process.argv.splice(2), {
|
|
195
|
+
homedir: os.homedir(),
|
|
196
|
+
env: process.env,
|
|
197
|
+
console,
|
|
198
|
+
fs: { ...fsPower, ...fsPromisesPower },
|
|
199
|
+
pathResolve: pathPower.resolve,
|
|
200
|
+
}).then(
|
|
201
|
+
_res => 0,
|
|
202
|
+
rej => {
|
|
203
|
+
console.error(`error running export-kernel-db:`, rej);
|
|
204
|
+
process.exit(process.exitCode || rej.exitCode || 1);
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
}
|